Repository: spack/spack

Issue:
concretizer: reuse ignores git-ref info
### Steps to reproduce

When multiple `@git.*` versions are mapped to the same effective version, `--reuse` reuses versions with different Git revisions:
```console
$ spack install libmonitor@git.22aa52c621534f12d401fa37f6963bfca7441e20=master
==> Installing libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx
==> No binary for libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx found: installing from source
==> No patches needed for libmonitor
==> libmonitor: Executing phase: 'autoreconf'
==> libmonitor: Executing phase: 'configure'
==> libmonitor: Executing phase: 'build'
==> libmonitor: Executing phase: 'install'
==> libmonitor: Successfully installed libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx
  Fetch: 1.37s.  Build: 2.49s.  Total: 3.86s.
[+] /opt/spack/opt/spack/linux-ubuntu22.04-zen2/gcc-11.2.0/libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx
$ spack install libmonitor@git.53115e000ab9a2a5d5f4b10d5e4ec7282e5c1550=master
[+] /opt/spack/opt/spack/linux-ubuntu22.04-zen2/gcc-11.2.0/libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx
```

This does not affect `--fresh` installs, only `--reuse`.

### Error message

A `@git.*` version should be highly specific, it should never be able to be satisfied (for `--reuse`) by any other non-identical version. In other words, the second install should actually install the requested Git commit.

<details>
<summary> <code>--debug</code> output: </summary>

```console
$ spack -d install libmonitor@git.53115e000ab9a2a5d5f4b10d5e4ec7282e5c1550=master
==> [2022-09-02-22:11:16.227950] Imported install from built-in commands
==> [2022-09-02-22:11:16.229167] Reading config from file /opt/spack/etc/spack/defaults/config.yaml
==> [2022-09-02-22:11:16.240339] Skipping nonexistent config path /opt/spack/etc/spack/defaults/linux/config.yaml
==> [2022-09-02-22:11:16.240377] Skipping nonexistent config path /etc/spack/config.yaml
==> [2022-09-02-22:11:16.240405] Skipping nonexistent config path /etc/spack/linux/config.yaml
==> [2022-09-02-22:11:16.240435] Skipping nonexistent config path /opt/spack/etc/spack/config.yaml
==> [2022-09-02-22:11:16.240464] Skipping nonexistent config path /opt/spack/etc/spack/linux/config.yaml
==> [2022-09-02-22:11:16.240495] Skipping nonexistent config path /root/.spack/config.yaml
==> [2022-09-02-22:11:16.240524] Skipping nonexistent config path /root/.spack/linux/config.yaml
==> [2022-09-02-22:11:16.240813] Imported install from built-in commands
==> [2022-09-02-22:11:16.241566] Reading config from file /opt/spack/etc/spack/defaults/bootstrap.yaml
==> [2022-09-02-22:11:16.245628] Skipping nonexistent config path /opt/spack/etc/spack/defaults/linux/bootstrap.yaml
==> [2022-09-02-22:11:16.245665] Skipping nonexistent config path /etc/spack/bootstrap.yaml
==> [2022-09-02-22:11:16.245690] Skipping nonexistent config path /etc/spack/linux/bootstrap.yaml
==> [2022-09-02-22:11:16.245721] Skipping nonexistent config path /opt/spack/etc/spack/bootstrap.yaml
==> [2022-09-02-22:11:16.245754] Skipping nonexistent config path /opt/spack/etc/spack/linux/bootstrap.yaml
==> [2022-09-02-22:11:16.245782] Skipping nonexistent config path /root/.spack/bootstrap.yaml
==> [2022-09-02-22:11:16.245810] Skipping nonexistent config path /root/.spack/linux/bootstrap.yaml
==> [2022-09-02-22:11:16.271432] Skipping nonexistent config path /opt/spack/etc/spack/defaults/upstreams.yaml
==> [2022-09-02-22:11:16.271469] Skipping nonexistent config path /opt/spack/etc/spack/defaults/linux/upstreams.yaml
==> [2022-09-02-22:11:16.271494] Skipping nonexistent config path /etc/spack/upstreams.yaml
==> [2022-09-02-22:11:16.271523] Skipping nonexistent config path /etc/spack/linux/upstreams.yaml
==> [2022-09-02-22:11:16.271553] Skipping nonexistent config path /opt/spack/etc/spack/upstreams.yaml
==> [2022-09-02-22:11:16.271583] Skipping nonexistent config path /opt/spack/etc/spack/linux/upstreams.yaml
==> [2022-09-02-22:11:16.271612] Skipping nonexistent config path /root/.spack/upstreams.yaml
==> [2022-09-02-22:11:16.271640] Skipping nonexistent config path /root/.spack/linux/upstreams.yaml
==> [2022-09-02-22:11:16.272030] Reading config from file /opt/spack/etc/spack/defaults/config.yaml
==> [2022-09-02-22:11:16.282820] Skipping nonexistent config path /opt/spack/etc/spack/defaults/linux/config.yaml
==> [2022-09-02-22:11:16.282856] Skipping nonexistent config path /etc/spack/config.yaml
==> [2022-09-02-22:11:16.282881] Skipping nonexistent config path /etc/spack/linux/config.yaml
==> [2022-09-02-22:11:16.282912] Skipping nonexistent config path /opt/spack/etc/spack/config.yaml
==> [2022-09-02-22:11:16.282942] Skipping nonexistent config path /opt/spack/etc/spack/linux/config.yaml
==> [2022-09-02-22:11:16.282971] Skipping nonexistent config path /root/.spack/config.yaml
==> [2022-09-02-22:11:16.282999] Skipping nonexistent config path /root/.spack/linux/config.yaml
==> [2022-09-02-22:11:16.283060] DATABASE LOCK TIMEOUT: 3s
==> [2022-09-02-22:11:16.283083] PACKAGE LOCK TIMEOUT: No timeout
==> [2022-09-02-22:11:16.283322] Reading config from file /opt/spack/etc/spack/defaults/bootstrap.yaml
==> [2022-09-02-22:11:16.287270] Skipping nonexistent config path /opt/spack/etc/spack/defaults/linux/bootstrap.yaml
==> [2022-09-02-22:11:16.287305] Skipping nonexistent config path /etc/spack/bootstrap.yaml
==> [2022-09-02-22:11:16.287333] Skipping nonexistent config path /etc/spack/linux/bootstrap.yaml
==> [2022-09-02-22:11:16.287363] Skipping nonexistent config path /opt/spack/etc/spack/bootstrap.yaml
==> [2022-09-02-22:11:16.287391] Skipping nonexistent config path /opt/spack/etc/spack/linux/bootstrap.yaml
==> [2022-09-02-22:11:16.287421] Skipping nonexistent config path /root/.spack/bootstrap.yaml
==> [2022-09-02-22:11:16.287449] Skipping nonexistent config path /root/.spack/linux/bootstrap.yaml
==> [2022-09-02-22:11:16.287559] DATABASE LOCK TIMEOUT: 3s
==> [2022-09-02-22:11:16.287583] PACKAGE LOCK TIMEOUT: No timeout
==> [2022-09-02-22:11:16.287623] [BOOTSTRAP CONFIG SCOPE] name=_builtin
==> [2022-09-02-22:11:16.287872] [BOOTSTRAP CONFIG SCOPE] name=defaults, path=/opt/spack/etc/spack/defaults
==> [2022-09-02-22:11:16.287897] [BOOTSTRAP CONFIG SCOPE] name=defaults/linux, path=/opt/spack/etc/spack/defaults/linux
==> [2022-09-02-22:11:16.287919] [BOOTSTRAP CONFIG SCOPE] name=bootstrap, path=/root/.spack/bootstrap/config
==> [2022-09-02-22:11:16.287942] [BOOTSTRAP CONFIG SCOPE] name=bootstrap/linux, path=/root/.spack/bootstrap/config/linux
==> [2022-09-02-22:11:16.288081] Skipping nonexistent config path /opt/spack/etc/spack/defaults/compilers.yaml
==> [2022-09-02-22:11:16.288111] Skipping nonexistent config path /opt/spack/etc/spack/defaults/linux/compilers.yaml
==> [2022-09-02-22:11:16.288141] Skipping nonexistent config path /root/.spack/bootstrap/config/compilers.yaml
==> [2022-09-02-22:11:16.288177] Reading config from file /root/.spack/bootstrap/config/linux/compilers.yaml
==> [2022-09-02-22:11:16.300845] [BOOTSTRAP ROOT SPEC] clingo-bootstrap@spack+python %gcc target=x86_64
==> [2022-09-02-22:11:16.300876] [BOOTSTRAP MODULE clingo] Try importing from Python
==> [2022-09-02-22:11:16.301016] Reading config from file /opt/spack/etc/spack/defaults/bootstrap.yaml
==> [2022-09-02-22:11:16.304945] Skipping nonexistent config path /opt/spack/etc/spack/defaults/linux/bootstrap.yaml
==> [2022-09-02-22:11:16.304981] Skipping nonexistent config path /root/.spack/bootstrap/config/bootstrap.yaml
==> [2022-09-02-22:11:16.407711] Reading config from file /opt/spack/etc/spack/defaults/config.yaml
==> [2022-09-02-22:11:16.418429] Skipping nonexistent config path /opt/spack/etc/spack/defaults/linux/config.yaml
==> [2022-09-02-22:11:16.418470] Skipping nonexistent config path /root/.spack/bootstrap/config/config.yaml
==> [2022-09-02-22:11:16.447249] [BOOTSTRAP MODULE clingo] The installed spec "clingo-bootstrap@spack+python %gcc target=x86_64 ^python@3.10 /2l45t4kw3cqqwj6vbxhfwhzlo6b3q2p4" provides the "clingo" Python module
==> [2022-09-02-22:11:16.447465] Reading config from file /opt/spack/etc/spack/defaults/concretizer.yaml
==> [2022-09-02-22:11:16.450219] Skipping nonexistent config path /opt/spack/etc/spack/defaults/linux/concretizer.yaml
==> [2022-09-02-22:11:16.450256] Skipping nonexistent config path /etc/spack/concretizer.yaml
==> [2022-09-02-22:11:16.450284] Skipping nonexistent config path /etc/spack/linux/concretizer.yaml
==> [2022-09-02-22:11:16.450317] Skipping nonexistent config path /opt/spack/etc/spack/concretizer.yaml
==> [2022-09-02-22:11:16.450348] Skipping nonexistent config path /opt/spack/etc/spack/linux/concretizer.yaml
==> [2022-09-02-22:11:16.450377] Skipping nonexistent config path /root/.spack/concretizer.yaml
==> [2022-09-02-22:11:16.450408] Skipping nonexistent config path /root/.spack/linux/concretizer.yaml
==> [2022-09-02-22:11:16.450475] Reading config from file /opt/spack/etc/spack/defaults/repos.yaml
==> [2022-09-02-22:11:16.451410] Skipping nonexistent config path /opt/spack/etc/spack/defaults/linux/repos.yaml
==> [2022-09-02-22:11:16.451443] Skipping nonexistent config path /etc/spack/repos.yaml
==> [2022-09-02-22:11:16.451473] Skipping nonexistent config path /etc/spack/linux/repos.yaml
==> [2022-09-02-22:11:16.451504] Skipping nonexistent config path /opt/spack/etc/spack/repos.yaml
==> [2022-09-02-22:11:16.451533] Skipping nonexistent config path /opt/spack/etc/spack/linux/repos.yaml
==> [2022-09-02-22:11:16.451562] Skipping nonexistent config path /root/.spack/repos.yaml
==> [2022-09-02-22:11:16.451590] Skipping nonexistent config path /root/.spack/linux/repos.yaml
==> [2022-09-02-22:11:16.498092] Reading config from file /opt/spack/etc/spack/defaults/config.yaml
==> [2022-09-02-22:11:16.508481] Skipping nonexistent config path /opt/spack/etc/spack/defaults/linux/config.yaml
==> [2022-09-02-22:11:16.508519] Skipping nonexistent config path /etc/spack/config.yaml
==> [2022-09-02-22:11:16.508545] Skipping nonexistent config path /etc/spack/linux/config.yaml
==> [2022-09-02-22:11:16.508577] Skipping nonexistent config path /opt/spack/etc/spack/config.yaml
==> [2022-09-02-22:11:16.508608] Skipping nonexistent config path /opt/spack/etc/spack/linux/config.yaml
==> [2022-09-02-22:11:16.508637] Skipping nonexistent config path /root/.spack/config.yaml
==> [2022-09-02-22:11:16.508667] Skipping nonexistent config path /root/.spack/linux/config.yaml
==> [2022-09-02-22:11:16.508984] Reading config from file /opt/spack/etc/spack/defaults/mirrors.yaml
==> [2022-09-02-22:11:16.509668] Skipping nonexistent config path /opt/spack/etc/spack/defaults/linux/mirrors.yaml
==> [2022-09-02-22:11:16.509702] Skipping nonexistent config path /etc/spack/mirrors.yaml
==> [2022-09-02-22:11:16.509729] Skipping nonexistent config path /etc/spack/linux/mirrors.yaml
==> [2022-09-02-22:11:16.509761] Skipping nonexistent config path /opt/spack/etc/spack/mirrors.yaml
==> [2022-09-02-22:11:16.509790] Skipping nonexistent config path /opt/spack/etc/spack/linux/mirrors.yaml
==> [2022-09-02-22:11:16.509818] Skipping nonexistent config path /root/.spack/mirrors.yaml
==> [2022-09-02-22:11:16.509846] Skipping nonexistent config path /root/.spack/linux/mirrors.yaml
==> [2022-09-02-22:11:16.510220] Checking existence of https://mirror.spack.io/build_cache/index.json
==> [2022-09-02-22:11:16.609080] Skipping nonexistent config path /opt/spack/etc/spack/defaults/compilers.yaml
==> [2022-09-02-22:11:16.609132] Skipping nonexistent config path /opt/spack/etc/spack/defaults/linux/compilers.yaml
==> [2022-09-02-22:11:16.609157] Skipping nonexistent config path /etc/spack/compilers.yaml
==> [2022-09-02-22:11:16.609178] Skipping nonexistent config path /etc/spack/linux/compilers.yaml
==> [2022-09-02-22:11:16.609200] Skipping nonexistent config path /opt/spack/etc/spack/compilers.yaml
==> [2022-09-02-22:11:16.609221] Skipping nonexistent config path /opt/spack/etc/spack/linux/compilers.yaml
==> [2022-09-02-22:11:16.609249] Skipping nonexistent config path /root/.spack/compilers.yaml
==> [2022-09-02-22:11:16.609289] Reading config from file /root/.spack/linux/compilers.yaml
==> [2022-09-02-22:11:16.612824] Reading config from file /opt/spack/etc/spack/defaults/packages.yaml
==> [2022-09-02-22:11:16.630582] Skipping nonexistent config path /opt/spack/etc/spack/defaults/linux/packages.yaml
==> [2022-09-02-22:11:16.630619] Skipping nonexistent config path /etc/spack/packages.yaml
==> [2022-09-02-22:11:16.630644] Skipping nonexistent config path /etc/spack/linux/packages.yaml
==> [2022-09-02-22:11:16.630666] Skipping nonexistent config path /opt/spack/etc/spack/packages.yaml
==> [2022-09-02-22:11:16.630687] Skipping nonexistent config path /opt/spack/etc/spack/linux/packages.yaml
==> [2022-09-02-22:11:16.630709] Skipping nonexistent config path /root/.spack/packages.yaml
==> [2022-09-02-22:11:16.630743] Skipping nonexistent config path /root/.spack/linux/packages.yaml
==> [2022-09-02-22:11:16.666837] version_equivalent(libmonitor, git.53115e000ab9a2a5d5f4b10d5e4ec7282e5c1550=master, master)
==> [2022-09-02-22:11:16.666883] version_equivalent(libmonitor, git.22aa52c621534f12d401fa37f6963bfca7441e20=master, master)
==> [2022-09-02-22:11:16.668159] Initializing the build queue from the build requests
==> [2022-09-02-22:11:16.668191] Initializing the build queue for libmonitor
==> [2022-09-02-22:11:16.668353] Processing dependencies for libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx: ('build', 'link', 'run')
==> [2022-09-02-22:11:16.668662] Pkg id libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx has the following dependents:
==> [2022-09-02-22:11:16.668842] Requeueing a build task for libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx with status 'queued'
==> [2022-09-02-22:11:16.668873] Ensure all dependencies know all dependents across specs
==> [2022-09-02-22:11:16.669783] Processing libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx: task=priority=0, status=dequeued, start=0, #dependencies=0
==> [2022-09-02-22:11:16.669966] Acquiring a write lock on libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx with timeout 1e-09
==> [2022-09-02-22:11:16.670088] libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx is now write locked
==> [2022-09-02-22:11:16.670724] Creating stage lock spack-stage-libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx
==> [2022-09-02-22:11:16.670820] Flagging libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx as installed
==> [2022-09-02-22:11:16.671096] Downgrading to a read lock on libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx with timeout 1e-09
==> [2022-09-02-22:11:16.671155] libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx is now read locked
[+] /opt/spack/opt/spack/linux-ubuntu22.04-zen2/gcc-11.2.0/libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx
==> [2022-09-02-22:11:16.671780] Releasing read lock on libmonitor-git.22aa52c621534f12d401fa37f6963bfca7441e20=master-ger7iq7brogiczeltlkedo72hw7srclx
```

</details>

### Information on your system

* **Spack:** 0.19.0.dev0 (69f7a8f4d142cf5b7142bf8f20925b214e56316a)
* **Python:** 3.10.4
* **Platform:** linux-ubuntu22.04-zen2
* **Concretizer:** clingo

### General information

- [X] I have run `spack debug report` and reported the version of Spack/Python/Platform
- [X] I have searched the issues of this repo and believe this is not a duplicate
- [X] I have run the failing commands in debug mode and reported the output


Discussion:

Resolving patch:
diff --git a/lib/spack/spack/fetch_strategy.py b/lib/spack/spack/fetch_strategy.py
--- a/lib/spack/spack/fetch_strategy.py
+++ b/lib/spack/spack/fetch_strategy.py
@@ -1543,7 +1543,19 @@ def for_package_version(pkg, version):
             ref_type: version.ref,
             "no_cache": True,
         }
+
         kwargs["submodules"] = getattr(pkg, "submodules", False)
+
+        # if we have a ref_version already, and it is a version from the package
+        # we can use that version's submodule specifications
+        if pkg.version.ref_version:
+            ref_version = spack.version.Version(pkg.version.ref_version[0])
+            ref_version_attributes = pkg.versions.get(ref_version)
+            if ref_version_attributes:
+                kwargs["submodules"] = ref_version_attributes.get(
+                    "submodules", kwargs["submodules"]
+                )
+
         fetcher = GitFetchStrategy(**kwargs)
         return fetcher
 
diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py
--- a/lib/spack/spack/solver/asp.py
+++ b/lib/spack/spack/solver/asp.py
@@ -1516,8 +1516,10 @@ def key_fn(item):
             # specs will be computed later
             version_preferences = packages_yaml.get(pkg_name, {}).get("version", [])
             for idx, v in enumerate(version_preferences):
+                # v can be a string so force it into an actual version for comparisons
+                ver = spack.version.Version(v)
                 self.declared_versions[pkg_name].append(
-                    DeclaredVersion(version=v, idx=idx, origin=version_provenance.packages_yaml)
+                    DeclaredVersion(version=ver, idx=idx, origin=version_provenance.packages_yaml)
                 )
 
         for spec in specs:
diff --git a/lib/spack/spack/version.py b/lib/spack/spack/version.py
--- a/lib/spack/spack/version.py
+++ b/lib/spack/spack/version.py
@@ -599,15 +599,33 @@ def satisfies(self, other):
         """A Version 'satisfies' another if it is at least as specific and has
         a common prefix.  e.g., we want gcc@4.7.3 to satisfy a request for
         gcc@4.7 so that when a user asks to build with gcc@4.7, we can find
-        a suitable compiler.
+        a suitable compiler. In the case of two GitVersions we require the ref_versions
+        to satisify one another and the versions to be an exact match.
         """
+
         self_cmp = self._cmp(other.ref_lookup)
         other_cmp = other._cmp(self.ref_lookup)
 
+        if other.is_ref:
+            # if other is a ref then satisfaction requires an exact version match
+            # i.e. the GitRef must match this.version for satisfaction
+            # this creates an asymmetric comparison:
+            #  - 'foo@main'.satisfies('foo@git.hash=main') == False
+            #  - 'foo@git.hash=main'.satisfies('foo@main') == True
+            version_match = self.version == other.version
+        elif self.is_ref:
+            # other is not a ref then it is a version base and we need to compare
+            # this.ref
+            version_match = self.ref_version == other.version
+        else:
+            # neither is a git ref.  We shouldn't ever be here, but if we are this variable
+            # is not meaningful and defaults to true
+            version_match = True
+
         # Do the final comparison
         nself = len(self_cmp)
         nother = len(other_cmp)
-        return nother <= nself and self_cmp[:nother] == other_cmp
+        return nother <= nself and self_cmp[:nother] == other_cmp and version_match
 
     def __repr__(self):
         return "GitVersion(" + repr(self.string) + ")"


Test patch:
diff --git a/lib/spack/spack/test/spec_syntax.py b/lib/spack/spack/test/spec_syntax.py
--- a/lib/spack/spack/test/spec_syntax.py
+++ b/lib/spack/spack/test/spec_syntax.py
@@ -837,3 +837,15 @@ def test_compare_abstract_specs(self):
         for a, b in itertools.product(specs, repeat=2):
             # Check that we can compare without raising an error
             assert a <= b or b < a
+
+    def test_git_ref_spec_equivalences(self, mock_packages, mock_stage):
+        s1 = sp.Spec("develop-branch-version@git.{hash}=develop".format(hash="a" * 40))
+        s2 = sp.Spec("develop-branch-version@git.{hash}=develop".format(hash="b" * 40))
+        s3 = sp.Spec("develop-branch-version@git.0.2.15=develop")
+        s_no_git = sp.Spec("develop-branch-version@develop")
+
+        assert s1.satisfies(s_no_git)
+        assert s2.satisfies(s_no_git)
+        assert not s_no_git.satisfies(s1)
+        assert not s2.satisfies(s1)
+        assert not s3.satisfies(s1)