Repository: NixOS/nix (C++)

Issue:
nix-hash process substitution example fails with `error: path '/dev/fd/63' is a symlink` on Arch Linux
**Describe the bug**

I cannot run the echo example for nix-hash `nix-hash --type sha256 --flat --base32 <(echo test)` from https://nix.dev/manual/nix/2.24/command-ref/nix-hash#examples. Then, I only get `error: path '/dev/fd/63' is a symlink`.

In fact, if I run any nix-hash command that uses the `<(...)` syntax for input, this fails.

**Steps To Reproduce**

1. run `nix-hash --type sha256 --flat --base32 <(echo test)`
2. see `error: path '/dev/fd/63' is a symlink`

**Expected behavior**

I expect this to work, as it is an example command. According to the examples `1lkgqb6fclns49861dwk9rzb6xnfkxbpws74mxnx01z9qyv1pjpj` should be the correct output.

**`nix-env --version` output**

nix-env (Nix) 2.24.7

**Additional context**

I'm on Arch Linux and used [their `nix` package](https://archlinux.org/packages/?name=nix) to install nix.

output of `stat <(echo test)`:

```
  File: /dev/fd/63 -> pipe:[360496]
  Size: 64              Blocks: 0          IO Block: 1024   symbolic link
Device: 0,22    Inode: 349772      Links: 1
Access: (0500/lr-x------)  Uid: ( 1000/   user)   Gid: ( 1000/   user)
Access: 2024-10-11 21:28:46.230728151 +0200
Modify: 2024-10-11 21:28:46.230728151 +0200
Change: 2024-10-11 21:28:46.230728151 +0200
 Birth: -
```

**Priorities**

Add :+1: to [issues you find important](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc).

Hashing values not in a regular file/directory
**Describe the bug**
```
$ git rev-list dfc876531f269950a4e183a4f77a813c421d7d64~2..dfc876531f269950a4e183a4f77a813c421d7d64                                                      
dfc876531f269950a4e183a4f77a813c421d7d64
d19a6675286a38edf8970459cbf454322f8151cb
```

@Ericson2314 in dfc876531 removed the ability for me to hash symlinks. Compare
```bash
$ nix run "github:nixos/nix?rev=d19a6675286a38edf8970459cbf454322f8151cb" -- --extra-experimental-features nix-command hash file --type sha256 --base32 <(printf "See, John Ericson, how things used to be good?")
1z1hfxr6398g4s1r10qc1lmwkaszp37p8b9v7qxhcb2i3vnz7z6a
```
with

```bash
$ nix run "github:nixos/nix?rev=$(git rev-parse dfc876531f269950a4e183a4f77a813c421d7d64)" -- --extra-experimental-features nix-command hash file --type sha256 --base32 <(printf "Why, John Ericson, why?")
error: path '«unknown»/proc/self/fd/11' is a symlink
```

This is pretty clearly related to some changes which introduced the `PosixSourceAccessor`. I've ran into issues in the past where the hash of a symlink was different than the hash of the contents pointed to by the symlink* so maybe these changes are uniformly good and shouldn't be revisited. But, since these changes break my workflow I'd like to figure out a solution. Of course, I could `printf "%s" "stuff I want to hash" > $(mktemp)` and then `nix hash file` that thing. But, I don't like creating files when I don't need one.

Maybe it makes sense to introduce a `nix hash string` which hashes the contents of STDIN?

\* In the sandbox I've had issues where the bind-mount of a `/nix/store/<symlink>` into the sandbox (where `/nix/store/<symlink>`→`/nix/store/<regular_file>`) mistakenly bind-mounts the regular file instead (because you can't actually bind-mount a symlink) where that symlink was used to prepare the squashfs for NixOS installation ISOs and the path copying which would occur on the host during the install phase would crash because the hashes of the store paths (symlink vs. regular file hashes) don't match - the root of the issue is that the Nix DB is used to get the "expected" hashes (symlink) while the "copy" operation hashes the _actual_ contents (regular file). This was fixed in https://github.com/NixOS/nix/pull/10456, I believe. We haven't seen this problem recently.

**Priorities**

Add :+1: to [issues you find important](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc).

`nix-store --add-fixed` no longer accepts symlinks
This was originally filed as a macos-specific bug ("nix-prefetch-git symlink error on macos"), but I think the root cause is a universal issue.

## Describe the bug

As of nix 2.20, you can't call `nix-store --add-fixed` on a path with a symlink parent.

## Steps To Reproduce

```shellsession
~/dev $ nix-shell -p 'nixVersions.nix_2_20'

[nix-shell:~/dev]$ mkdir -p foo/bar && ln -s foo foo-link

[nix-shell:~/dev]$ nix-store --add-fixed --recursive sha256 foo-link/bar
error: path '«unknown»/Users/jon/dev/foo-link' is a symlink
```
The same command works fine with 2.19


This causes problems with tools like nix-prefetch-git, especially on macOS where it clones the repo into /tmp, which is actually a symlink to /private/tmp.


The change-in-behaviour presumably originates from https://github.com/NixOS/nix/commit/83c067c0fa0cc5a2dca440e5c986afe40b163802


<details>
<summary>Original issue details</summary>


## Describe the bug

nix-prefetch-git doesn't seem to work for me on macos with the 24.11-beta tag - it complains that /tmp is a symlink (which it is, it links to /private/tmp).

## Steps To Reproduce

```shellsession
$ nix run github:nixos/nixpkgs/24.11-beta#nix-prefetch-git -- --url https://github.com/IFTTT/polo.git --rev 316aa2ac210a45a7fc400ab921831493d5dd21b8 --hash sha256
Initialized empty Git repository in /private/tmp/git-checkout-tmp-MzN6ZcNg/polo-316aa2a/.git/
remote: Enumerating objects: 51, done.
remote: Counting objects: 100% (51/51), done.
remote: Compressing objects: 100% (42/42), done.
remote: Total 51 (delta 8), reused 19 (delta 5), pack-reused 0 (from 0)
Unpacking objects: 100% (51/51), 19.57 KiB | 668.00 KiB/s, done.
From https://github.com/IFTTT/polo
 * branch            HEAD       -> FETCH_HEAD
Switched to a new branch 'fetchgit'
removing `.git'...
error: path '/tmp' is a symlink
```

## Expected behavior


Successfully fetches a git repo into the store

## Additional context

The same command works fine with 24.05:

```
$ nix run github:nixos/nixpkgs/24.05#nix-prefetch-git -- --url https://github.com/IFTTT/polo.git --rev 316aa2ac210a45a7fc400ab921831493d5dd21b8 --hash sha256
Initialized empty Git repository in /private/tmp/git-checkout-tmp-22f9iAdj/polo-316aa2a/.git/
remote: Enumerating objects: 51, done.
remote: Counting objects: 100% (51/51), done.
remote: Compressing objects: 100% (42/42), done.
remote: Total 51 (delta 8), reused 19 (delta 5), pack-reused 0 (from 0)
Unpacking objects: 100% (51/51), 19.57 KiB | 715.00 KiB/s, done.
From https://github.com/IFTTT/polo
 * branch            HEAD       -> FETCH_HEAD
Switched to a new branch 'fetchgit'
removing `.git'...

git revision is 316aa2ac210a45a7fc400ab921831493d5dd21b8
path is /nix/store/nljjjs2idzm9j5iy9swppd37z6aq96fb-polo-316aa2a
git human-readable version is -- none --
Commit date is 2022-04-04 16:29:34 -0700
hash is 1vikzclg64lpydfwvz2imzwvl2f2vv1iq9ww43ah31p84w2xfmih
{
  "url": "https://github.com/IFTTT/polo.git",
  "rev": "316aa2ac210a45a7fc400ab921831493d5dd21b8",
  "date": "2022-04-04T16:29:34-07:00",
  "path": "/nix/store/nljjjs2idzm9j5iy9swppd37z6aq96fb-polo-316aa2a",
  "sha256": "1vikzclg64lpydfwvz2imzwvl2f2vv1iq9ww43ah31p84w2xfmih",
  "hash": "sha256-MFbXBSfohgHVIJwnHMPewgm6+a9R/M1d85cS8yj7M+4=",
  "fetchLFS": false,
  "fetchSubmodules": false,
  "deepClone": false,
  "leaveDotGit": false
}
```

I'd originally posted this in [nixpkgs](https://github.com/NixOS/nixpkgs/issues/358113), but then bisected it down to [9a94e07](https://github.com/NixOS/nixpkgs/commit/9a94e073bfc38ad5dcb1d5f35e9c6fbb3886ead4), so I'm guessing the problem is actually in nix itself..?


## Metadata

<!-- Please insert the output of running `nix-env --version` below this line -->

```
nix-env (Nix) 2.18.8
```

(though I believe that nix-prefetch-git is using 2.24, at least in the 22.11 release

</details>

## Checklist

<!-- make sure this issue is not redundant or obsolete -->

- [x] checked [latest Nix manual] \([source])
- [x] checked [open bug issues and pull requests] for possible duplicates

Add :+1: to [issues you find important](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc).



Discussion: I'm also experiencing this error now on macOS, except that the error reports that `/var` is a symlink.
I think it's also what broke [swiftpm2nix](https://github.com/ofalvai/nixpkgs/blob/269df0a9d807ec147fea4d4a940a5d05966832ab/pkgs/development/compilers/swift/swiftpm2nix/swiftpm2nix.sh#L26) recently. That error surfaces as:

```
-- Fetching Commander
Initialized empty Git repository in /private/tmp/git-checkout-tmp-G27B0tSB/Commander-4b6133c/.git/
remote: Enumerating objects: 41, done.
remote: Counting objects: 100% (41/41), done.
remote: Compressing objects: 100% (36/36), done.
remote: Total 41 (delta 1), reused 16 (delta 1), pack-reused 0 (from 0)
Unpacking objects: 100% (41/41), 31.18 KiB | 1.04 MiB/s, done.
From https://github.com/kylef/Commander
 * tag               0.9.1      -> FETCH_HEAD
Switched to a new branch 'fetchgit'
removing `.git'...
error: path '/tmp' is a symlink
```

This makes it impossible to use `nix-hash` on any file in `$TMPDIR` on macOS, because `/tmp` is a symlink to `/private/tmp` and `/var` is a symlink to `/private/var`.

As a consequence, this makes it impossible to use `nix-prefetch-git` (and, therefore, `cabal2nix`) on macOS without carefully setting environment variables.

I can't check when this bug was introduced because all Nix versions below 2.24 have been removed from nixpkgs (lol).
Also related to #11681, #11941?

This is a huge issue on MacOS where a lot of paths are behind symlinks.

Assuming this is an intentional behaviour change, I've submitted https://github.com/NixOS/nixpkgs/pull/358685 so that the nix-prefetch scripts resolve /tmp to a real directory before calling `nix-store --add-fixed` on it
Speaking more generally, is it expected that you can't call `nix-store --add-fixed` on a path with a symlink parent as-of nix.2.20, on nixos as well as macos ?

```shellsession
~/dev $ nix-shell -p 'nixVersions.nix_2_20'

[nix-shell:~/dev]$ mkdir -p foo/bar && ln -s foo foo-link

[nix-shell:~/dev]$ nix-store --add-fixed --recursive sha256 foo-link/bar
error: path '«unknown»/Users/jon/dev/foo-link' is a symlink
```

I don't see a relevant-sounding change listed in the release notes, not sure if it's a bug or feature.
The last comment gives a way to reproduce on linux easily.

* 83c067c0fa0cc5a2dca440e5c986afe40b163802
* 345f79d01676680f2d4ef8803790896a190c855b
* 504e4fc4576dc6a4cd5c083a3bf7b80dfb0ca220
* 57246c4c3802920e6167fd540dae2a0abca97f15

The last 2 even mentioning symlinks in their commit messages

<details><summary>Bisection log</summary>

```
# bad: [16e1ff3bcb5e785cce6bff8d0745e2a73fbd99de] Mark as stable
# good: [5b99c823ef95ba5c642ae105815d5acd4f093aa3] Mark official release
git bisect start '2.20.0' '2.19.0'
# good: [6832d18ac734f4b855f97c07b158491dd01cefcd] Merge pull request #9399 from edolstra/revert-vlas
git bisect good 6832d18ac734f4b855f97c07b158491dd01cefcd
# bad: [16a77767cdf90cbd53ff451c93fde8e5d89bdb1f] Merge pull request #9635 from 9999years/document-makefile-variables
git bisect bad 16a77767cdf90cbd53ff451c93fde8e5d89bdb1f
# good: [9817788f6b25fefa731b838391f859e886ae0d24] Merge pull request #9518 from NixOS/fix-static-build
git bisect good 9817788f6b25fefa731b838391f859e886ae0d24
# bad: [b1c633c6bb2e2dbc8a2891edf0d0c6f9d31d890b] Merge pull request #9600 from SharzyL/fix_nix_copy
git bisect bad b1c633c6bb2e2dbc8a2891edf0d0c6f9d31d890b
# bad: [7cdc8786d9f57410acbec088e13e46ce10cdce35] Merge pull request #9568 from hercules-ci/revert-9553
git bisect bad 7cdc8786d9f57410acbec088e13e46ce10cdce35
# good: [139982997eec493a0f74105c427953f6be77da6d] Merge pull request #9560 from obsidiansystems/serve-proto-unkeyed-valid-path-info-serializer
git bisect good 139982997eec493a0f74105c427953f6be77da6d
# bad: [2bd83225004012af97d2d5977dc1de952f60aa8d] Update src/libfetchers/filtering-input-accessor.hh
git bisect bad 2bd83225004012af97d2d5977dc1de952f60aa8d
# skip: [345f79d01676680f2d4ef8803790896a190c855b] Check that we can't follow symlinks outside of the allowed paths
git bisect skip 345f79d01676680f2d4ef8803790896a190c855b
# skip: [504e4fc4576dc6a4cd5c083a3bf7b80dfb0ca220] CanonPath: Support std::hash
git bisect skip 504e4fc4576dc6a4cd5c083a3bf7b80dfb0ca220
# skip: [83c067c0fa0cc5a2dca440e5c986afe40b163802] PosixSourceAccessor: Don't follow any symlinks
git bisect skip 83c067c0fa0cc5a2dca440e5c986afe40b163802
# bad: [57246c4c3802920e6167fd540dae2a0abca97f15] PosixSourceAccessor: Cache lstat() calls
git bisect bad 57246c4c3802920e6167fd540dae2a0abca97f15
# good: [8cafc754d845529a78595d1196769257ee23ca56] Move access control from FSInputAccessor to FilteringInputAccessor
git bisect good 8cafc754d845529a78595d1196769257ee23ca56
# only skipped commits left to test
# possible first bad commit: [57246c4c3802920e6167fd540dae2a0abca97f15] PosixSourceAccessor: Cache lstat() calls
# possible first bad commit: [504e4fc4576dc6a4cd5c083a3bf7b80dfb0ca220] CanonPath: Support std::hash
# possible first bad commit: [83c067c0fa0cc5a2dca440e5c986afe40b163802] PosixSourceAccessor: Don't follow any symlinks
# possible first bad commit: [345f79d01676680f2d4ef8803790896a190c855b] Check that we can't follow symlinks outside of the allowed paths
```
</details>
I believe this is the same issue as https://github.com/NixOS/nix/issues/11681
Full list of duplicates (that I can find):

- https://github.com/NixOS/nixpkgs/issues/358113
- #11756
- #11681

Possibly related:
- #9253
- #9463
- #11630

This bug appears to have a wide blast radius. This is the exact reason there was so much resistance to moving the Nixpkgs default Nix version away from 2.18 and makes the decision to [remove versions of Nix older than 2.24 from Nixpkgs (despite known regressions with no workarounds)](https://github.com/NixOS/nixpkgs/pull/359215#issuecomment-2515965863) so much more frustrating.



Fix (patch):
diff --git a/doc/manual/source/command-ref/nix-store/add-fixed.md b/doc/manual/source/command-ref/nix-store/add-fixed.md
index bebf15026a6..2ea90a13592 100644
--- a/doc/manual/source/command-ref/nix-store/add-fixed.md
+++ b/doc/manual/source/command-ref/nix-store/add-fixed.md
@@ -21,6 +21,9 @@ This operation has the following options:
   Use recursive instead of flat hashing mode, used when adding
   directories to the store.
 
+  *paths* that refer to symlinks are not dereferenced, but added to the store
+  as symlinks with the same target.
+
 {{#include ./opt-common.md}}
 
 {{#include ../opt-common.md}}
diff --git a/doc/manual/source/command-ref/nix-store/add.md b/doc/manual/source/command-ref/nix-store/add.md
index 87d504cd333..ab474072311 100644
--- a/doc/manual/source/command-ref/nix-store/add.md
+++ b/doc/manual/source/command-ref/nix-store/add.md
@@ -11,6 +11,9 @@
 The operation `--add` adds the specified paths to the Nix store. It
 prints the resulting paths in the Nix store on standard output.
 
+*paths* that refer to symlinks are not dereferenced, but added to the store
+as symlinks with the same target.
+
 {{#include ./opt-common.md}}
 
 {{#include ../opt-common.md}}
diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc
index 793eb2d9fb8..923220fd0b2 100644
--- a/src/libutil/file-system.cc
+++ b/src/libutil/file-system.cc
@@ -766,4 +766,19 @@ bool isExecutableFileAmbient(const fs::path & exe) {
         ) == 0;
 }
 
+std::filesystem::path makeParentCanonical(const std::filesystem::path & rawPath)
+{
+    std::filesystem::path path(absPath(rawPath));;
+    try {
+        auto parent = path.parent_path();
+        if (parent == path) {
+            // `path` is a root directory => trivially canonical
+            return parent;
+        }
+        return std::filesystem::canonical(parent) / path.filename();
+    } catch (fs::filesystem_error & e) {
+        throw SysError("canonicalising parent path of '%1%'", path);
+    }
 }
+
+} // namespace nix
diff --git a/src/libutil/file-system.hh b/src/libutil/file-system.hh
index 3c49181a0ad..7fdaba8111e 100644
--- a/src/libutil/file-system.hh
+++ b/src/libutil/file-system.hh
@@ -143,6 +143,23 @@ inline bool symlink_exists(const std::filesystem::path & path) {
 
 } // namespace fs
 
+/**
+ * Canonicalize a path except for the last component.
+ *
+ * This is useful for getting the canonical location of a symlink.
+ *
+ * Consider the case where `foo/l` is a symlink. `canonical("foo/l")` will
+ * resolve the symlink `l` to its target.
+ * `makeParentCanonical("foo/l")` will not resolve the symlink `l` to its target,
+ * but does ensure that the returned parent part of the path, `foo` is resolved
+ * to `canonical("foo")`, and can therefore be retrieved without traversing any
+ * symlinks.
+ *
+ * If a relative path is passed, it will be made absolute, so that the parent
+ * can always be canonicalized.
+ */
+std::filesystem::path makeParentCanonical(const std::filesystem::path & path);
+
 /**
  * A version of pathExists that returns false on a permission error.
  * Useful for inferring default paths across directories that might not
diff --git a/src/libutil/posix-source-accessor.hh b/src/libutil/posix-source-accessor.hh
index 40f60bb54b8..5d491e633ce 100644
--- a/src/libutil/posix-source-accessor.hh
+++ b/src/libutil/posix-source-accessor.hh
@@ -43,13 +43,25 @@ struct PosixSourceAccessor : virtual SourceAccessor
     std::optional<std::filesystem::path> getPhysicalPath(const CanonPath & path) override;
 
     /**
-     * Create a `PosixSourceAccessor` and `CanonPath` corresponding to
+     * Create a `PosixSourceAccessor` and `SourcePath` corresponding to
      * some native path.
      *
      * The `PosixSourceAccessor` is rooted as far up the tree as
      * possible, (e.g. on Windows it could scoped to a drive like
      * `C:\`). This allows more `..` parent accessing to work.
      *
+     * @note When `path` is trusted user input, canonicalize it using
+     * `std::filesystem::canonical`, `makeParentCanonical`, `std::filesystem::weakly_canonical`, etc,
+     * as appropriate for the use case. At least weak canonicalization is
+     * required for the `SourcePath` to do anything useful at the location it
+     * points to.
+     *
+     * @note A canonicalizing behavior is not built in `createAtRoot` so that
+     * callers do not accidentally introduce symlink-related security vulnerabilities.
+     * Furthermore, `createAtRoot` does not know whether the file pointed to by
+     * `path` should be resolved if it is itself a symlink. In other words,
+     * `createAtRoot` can not decide between aforementioned `canonical`, `makeParentCanonical`, etc. for its callers.
+     *
      * See
      * [`std::filesystem::path::root_path`](https://en.cppreference.com/w/cpp/filesystem/path/root_path)
      * and
diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc
index b731b25afed..99bb2c72601 100644
--- a/src/nix-store/nix-store.cc
+++ b/src/nix-store/nix-store.cc
@@ -183,9 +183,9 @@ static void opAdd(Strings opFlags, Strings opArgs)
     if (!opFlags.empty()) throw UsageError("unknown flag");
 
     for (auto & i : opArgs) {
-        auto [accessor, canonPath] = PosixSourceAccessor::createAtRoot(i);
+        auto sourcePath = PosixSourceAccessor::createAtRoot(makeParentCanonical(i));
         cout << fmt("%s\n", store->printStorePath(store->addToStore(
-            std::string(baseNameOf(i)), {accessor, canonPath})));
+            std::string(baseNameOf(i)), sourcePath)));
     }
 }
 
@@ -207,10 +207,10 @@ static void opAddFixed(Strings opFlags, Strings opArgs)
     opArgs.pop_front();
 
     for (auto & i : opArgs) {
-        auto [accessor, canonPath] = PosixSourceAccessor::createAtRoot(i);
+        auto sourcePath = PosixSourceAccessor::createAtRoot(makeParentCanonical(i));
         std::cout << fmt("%s\n", store->printStorePath(store->addToStoreSlow(
             baseNameOf(i),
-            {accessor, canonPath},
+            sourcePath,
             method,
             hashAlgo).path));
     }
diff --git a/src/nix/add-to-store.cc b/src/nix/add-to-store.cc
index 5c08f761662..7f15de374eb 100644
--- a/src/nix/add-to-store.cc
+++ b/src/nix/add-to-store.cc
@@ -37,13 +37,13 @@ struct CmdAddToStore : MixDryRun, StoreCommand
     {
         if (!namePart) namePart = baseNameOf(path);
 
-        auto [accessor, path2] = PosixSourceAccessor::createAtRoot(path);
+        auto sourcePath = PosixSourceAccessor::createAtRoot(makeParentCanonical(path));
 
         auto storePath = dryRun
             ? store->computeStorePath(
-                *namePart, {accessor, path2}, caMethod, hashAlgo, {}).first
+                *namePart, sourcePath, caMethod, hashAlgo, {}).first
             : store->addToStoreSlow(
-                *namePart, {accessor, path2}, caMethod, hashAlgo, {}).path;
+                *namePart, sourcePath, caMethod, hashAlgo, {}).path;
 
         logger->cout("%s", store->printStorePath(storePath));
     }
diff --git a/src/nix/hash.cc b/src/nix/hash.cc
index 416cd19b317..eac421d1260 100644
--- a/src/nix/hash.cc
+++ b/src/nix/hash.cc
@@ -87,18 +87,35 @@ struct CmdHashBase : Command
                     return std::make_unique<HashSink>(hashAlgo);
             };
 
-            auto path2 = PosixSourceAccessor::createAtRoot(path);
+            auto makeSourcePath = [&]() -> SourcePath {
+                return PosixSourceAccessor::createAtRoot(makeParentCanonical(path));
+            };
+
             Hash h { HashAlgorithm::SHA256 }; // throwaway def to appease C++
             switch (mode) {
             case FileIngestionMethod::Flat:
+            {
+                // While usually we could use the some code as for NixArchive,
+                // the Flat method needs to support FIFOs, such as those
+                // produced by bash process substitution, e.g.:
+                //     nix hash --mode flat <(echo hi)
+                // Also symlinks semantics are unambiguous in the flat case,
+                // so we don't need to go low-level, or reject symlink `path`s.
+                auto hashSink = makeSink();
+                readFile(path, *hashSink);
+                h = hashSink->finish().first;
+                break;
+            }
             case FileIngestionMethod::NixArchive:
             {
+                auto sourcePath = makeSourcePath();
                 auto hashSink = makeSink();
-                dumpPath(path2, *hashSink, (FileSerialisationMethod) mode);
+                dumpPath(sourcePath, *hashSink, (FileSerialisationMethod) mode);
                 h = hashSink->finish().first;
                 break;
             }
             case FileIngestionMethod::Git: {
+                auto sourcePath = makeSourcePath();
                 std::function<git::DumpHook> hook;
                 hook = [&](const SourcePath & path) -> git::TreeEntry {
                     auto hashSink = makeSink();
@@ -109,7 +126,7 @@ struct CmdHashBase : Command
                         .hash = hash,
                     };
                 };
-                h = hook(path2).hash;
+                h = hook(sourcePath).hash;
                 break;
             }
             }