From 0157c153f8bc79c1e67ae0b67746e0ddb444bebd Mon Sep 17 00:00:00 2001
From: Ruben Arts <ruben.arts@hotmail.com>
Date: Tue, 6 Aug 2024 08:43:55 +0200
Subject: [PATCH 01/17] fix: parse ~= as version not as path

---
 .../src/match_spec/parse.rs                   | 23 +++++++++
 ...arse__tests__test_from_string_Lenient.snap |  3 ++
 ...parse__tests__test_from_string_Strict.snap |  3 ++
 ...ts__test_nameless_from_string_Lenient.snap | 51 +++++++++++++++++++
 ...sts__test_nameless_from_string_Strict.snap | 48 +++++++++++++++++
 crates/rattler_conda_types/src/utils/path.rs  |  2 +-
 6 files changed, 129 insertions(+), 1 deletion(-)

diff --git a/crates/rattler_conda_types/src/match_spec/parse.rs b/crates/rattler_conda_types/src/match_spec/parse.rs
index 11e7030ce..2d8f639fb 100644
--- a/crates/rattler_conda_types/src/match_spec/parse.rs
+++ b/crates/rattler_conda_types/src/match_spec/parse.rs
@@ -966,6 +966,7 @@ mod tests {
             // subdir in brackets take precedence
             "conda-forge/linux-32::python[version=3.9, subdir=linux-64]",
             "conda-forge/linux-32::python ==3.9[subdir=linux-64, build_number=\"0\"]",
+            "rust ~=1.2.3"
         ];
 
         let evaluated: IndexMap<_, _> = specs
@@ -1001,6 +1002,28 @@ mod tests {
         let specs = [
             "2.7|>=3.6",
             "https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2",
+            "~=1.2.3",
+            "*.* mkl",
+            "C:\\Users\\user\\conda-bld\\linux-64\\foo-1.0-py27_0.tar.bz2",
+            "=1.0=py27_0",
+            "==1.0=py27_0",
+            "https://conda.anaconda.org/conda-forge/linux-64/py-rattler-0.6.1-py39h8169da8_0.conda",
+            "https://repo.prefix.dev/ruben-arts/linux-64/boost-cpp-1.78.0-h75c5d50_1.tar.bz2",
+            "3.8.* *_cpython",
+            "=*=cuda*",
+            ">=1!164.3095,<1!165",
+            "/home/user/conda-bld/linux-64/foo-1.0-py27_0.tar.bz2",
+            "[version=1.0.*]",
+            "[version=1.0.*, build_number=\">6\"]",
+            "==2.7.*.*|>=3.6",
+            "3.9",
+            "*",
+            "[version=3.9]",
+            "[version=3.9]",
+            "[version=3.9, subdir=linux-64]",
+            // subdir in brackets take precedence
+            "[version=3.9, subdir=linux-64]",
+            "==3.9[subdir=linux-64, build_number=\"0\"]",
         ];
 
         let evaluated: IndexMap<_, _> = specs
diff --git a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap
index 9d6bfc745..f12b4a71e 100644
--- a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap
+++ b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap
@@ -98,3 +98,6 @@ python=*:
     base_url: "https://conda.anaconda.org/conda-forge/"
     name: conda-forge
   subdir: linux-64
+rust ~=1.2.3:
+  name: rust
+  version: ~=1.2.3
diff --git a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap
index 8a77decf3..6d6256054 100644
--- a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap
+++ b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap
@@ -90,3 +90,6 @@ python=*:
     base_url: "https://conda.anaconda.org/conda-forge/"
     name: conda-forge
   subdir: linux-64
+rust ~=1.2.3:
+  name: rust
+  version: ~=1.2.3
diff --git a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_nameless_from_string_Lenient.snap b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_nameless_from_string_Lenient.snap
index 81c734197..78a021e2f 100644
--- a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_nameless_from_string_Lenient.snap
+++ b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_nameless_from_string_Lenient.snap
@@ -6,3 +6,54 @@ expression: evaluated
   version: "==2.7|>=3.6"
 "https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2":
   url: "https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2"
+~=1.2.3:
+  version: ~=1.2.3
+"*.* mkl":
+  version: "*"
+  build: mkl
+"C:\\Users\\user\\conda-bld\\linux-64\\foo-1.0-py27_0.tar.bz2":
+  url: "file:///C:/Users/user/conda-bld/linux-64/foo-1.0-py27_0.tar.bz2"
+"=1.0=py27_0":
+  version: "==1.0"
+  build: py27_0
+"==1.0=py27_0":
+  version: "==1.0"
+  build: py27_0
+"https://conda.anaconda.org/conda-forge/linux-64/py-rattler-0.6.1-py39h8169da8_0.conda":
+  url: "https://conda.anaconda.org/conda-forge/linux-64/py-rattler-0.6.1-py39h8169da8_0.conda"
+"https://repo.prefix.dev/ruben-arts/linux-64/boost-cpp-1.78.0-h75c5d50_1.tar.bz2":
+  url: "https://repo.prefix.dev/ruben-arts/linux-64/boost-cpp-1.78.0-h75c5d50_1.tar.bz2"
+3.8.* *_cpython:
+  version: 3.8.*
+  build: "*_cpython"
+"=*=cuda*":
+  version: "*"
+  build: cuda*
+">=1!164.3095,<1!165":
+  version: ">=1!164.3095,<1!165"
+/home/user/conda-bld/linux-64/foo-1.0-py27_0.tar.bz2:
+  url: "file:///home/user/conda-bld/linux-64/foo-1.0-py27_0.tar.bz2"
+"[version=1.0.*]":
+  version: 1.0.*
+"[version=1.0.*, build_number=\">6\"]":
+  version: 1.0.*
+  build_number:
+    op: Gt
+    rhs: 6
+"==2.7.*.*|>=3.6":
+  version: 2.7.*|>=3.6
+"3.9":
+  version: "==3.9"
+"*":
+  version: "*"
+"[version=3.9]":
+  version: "==3.9"
+"[version=3.9, subdir=linux-64]":
+  version: "==3.9"
+  subdir: linux-64
+"==3.9[subdir=linux-64, build_number=\"0\"]":
+  version: "==3.9"
+  build_number:
+    op: Eq
+    rhs: 0
+  subdir: linux-64
diff --git a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_nameless_from_string_Strict.snap b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_nameless_from_string_Strict.snap
index 81c734197..75a46be02 100644
--- a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_nameless_from_string_Strict.snap
+++ b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_nameless_from_string_Strict.snap
@@ -6,3 +6,51 @@ expression: evaluated
   version: "==2.7|>=3.6"
 "https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2":
   url: "https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2"
+~=1.2.3:
+  version: ~=1.2.3
+"*.* mkl":
+  version: "*"
+  build: mkl
+"C:\\Users\\user\\conda-bld\\linux-64\\foo-1.0-py27_0.tar.bz2":
+  url: "file:///C:/Users/user/conda-bld/linux-64/foo-1.0-py27_0.tar.bz2"
+"=1.0=py27_0":
+  error: "The build string '=py27_0' is not valid, it can only contain alphanumeric characters and underscores"
+"==1.0=py27_0":
+  error: "The build string '=py27_0' is not valid, it can only contain alphanumeric characters and underscores"
+"https://conda.anaconda.org/conda-forge/linux-64/py-rattler-0.6.1-py39h8169da8_0.conda":
+  url: "https://conda.anaconda.org/conda-forge/linux-64/py-rattler-0.6.1-py39h8169da8_0.conda"
+"https://repo.prefix.dev/ruben-arts/linux-64/boost-cpp-1.78.0-h75c5d50_1.tar.bz2":
+  url: "https://repo.prefix.dev/ruben-arts/linux-64/boost-cpp-1.78.0-h75c5d50_1.tar.bz2"
+3.8.* *_cpython:
+  version: 3.8.*
+  build: "*_cpython"
+"=*=cuda*":
+  error: "The build string '=cuda*' is not valid, it can only contain alphanumeric characters and underscores"
+">=1!164.3095,<1!165":
+  version: ">=1!164.3095,<1!165"
+/home/user/conda-bld/linux-64/foo-1.0-py27_0.tar.bz2:
+  url: "file:///home/user/conda-bld/linux-64/foo-1.0-py27_0.tar.bz2"
+"[version=1.0.*]":
+  version: 1.0.*
+"[version=1.0.*, build_number=\">6\"]":
+  version: 1.0.*
+  build_number:
+    op: Gt
+    rhs: 6
+"==2.7.*.*|>=3.6":
+  error: "invalid version constraint: regex constraints are not supported"
+"3.9":
+  version: "==3.9"
+"*":
+  version: "*"
+"[version=3.9]":
+  version: "==3.9"
+"[version=3.9, subdir=linux-64]":
+  version: "==3.9"
+  subdir: linux-64
+"==3.9[subdir=linux-64, build_number=\"0\"]":
+  version: "==3.9"
+  build_number:
+    op: Eq
+    rhs: 0
+  subdir: linux-64
diff --git a/crates/rattler_conda_types/src/utils/path.rs b/crates/rattler_conda_types/src/utils/path.rs
index a4531aa38..f1c6a1a2c 100644
--- a/crates/rattler_conda_types/src/utils/path.rs
+++ b/crates/rattler_conda_types/src/utils/path.rs
@@ -9,7 +9,7 @@ pub(crate) fn is_path(path: &str) -> bool {
     // Check if the path starts with a common path prefix
     if path.starts_with("./")
         || path.starts_with("..")
-        || path.starts_with('~')
+        || path.starts_with('~') && !path.starts_with("~=")
         || path.starts_with('/')
         || path.starts_with("\\\\")
         || path.starts_with("//")

From 40955929a9ee0bd955a55188f4504dce0dcc4a5a Mon Sep 17 00:00:00 2001
From: Ruben Arts <ruben.arts@hotmail.com>
Date: Tue, 6 Aug 2024 08:46:23 +0200
Subject: [PATCH 02/17] fmt

---
 crates/rattler_conda_types/src/match_spec/parse.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crates/rattler_conda_types/src/match_spec/parse.rs b/crates/rattler_conda_types/src/match_spec/parse.rs
index 2d8f639fb..1fdaccf1c 100644
--- a/crates/rattler_conda_types/src/match_spec/parse.rs
+++ b/crates/rattler_conda_types/src/match_spec/parse.rs
@@ -966,7 +966,7 @@ mod tests {
             // subdir in brackets take precedence
             "conda-forge/linux-32::python[version=3.9, subdir=linux-64]",
             "conda-forge/linux-32::python ==3.9[subdir=linux-64, build_number=\"0\"]",
-            "rust ~=1.2.3"
+            "rust ~=1.2.3",
         ];
 
         let evaluated: IndexMap<_, _> = specs

From 9ca625511f7bb5ed33e211382f02f502bd5a2dab Mon Sep 17 00:00:00 2001
From: Ruben Arts <ruben.arts@hotmail.com>
Date: Tue, 6 Aug 2024 08:48:57 +0200
Subject: [PATCH 03/17] fix: check on ~/ instead of avoiding the version style

---
 crates/rattler_conda_types/src/utils/path.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crates/rattler_conda_types/src/utils/path.rs b/crates/rattler_conda_types/src/utils/path.rs
index f1c6a1a2c..b88735ef5 100644
--- a/crates/rattler_conda_types/src/utils/path.rs
+++ b/crates/rattler_conda_types/src/utils/path.rs
@@ -9,7 +9,7 @@ pub(crate) fn is_path(path: &str) -> bool {
     // Check if the path starts with a common path prefix
     if path.starts_with("./")
         || path.starts_with("..")
-        || path.starts_with('~') && !path.starts_with("~=")
+        || path.starts_with("~/")
         || path.starts_with('/')
         || path.starts_with("\\\\")
         || path.starts_with("//")

From 95402139b4c9e29364e65718b75c5c16feb01d4b Mon Sep 17 00:00:00 2001
From: Ruben Arts <ruben.arts@hotmail.com>
Date: Tue, 6 Aug 2024 11:28:06 +0200
Subject: [PATCH 04/17] fix: tilde path for channel

---
 crates/rattler_conda_types/Cargo.toml         |  3 +-
 crates/rattler_conda_types/src/channel/mod.rs | 26 +++++-----
 .../src/match_spec/parse.rs                   | 25 +++++++---
 ...arse__tests__test_from_string_Lenient.snap | 10 ++++
 ...parse__tests__test_from_string_Strict.snap | 10 ++++
 crates/rattler_conda_types/src/utils/path.rs  | 49 +++++++++++++++++++
 6 files changed, 100 insertions(+), 23 deletions(-)

diff --git a/crates/rattler_conda_types/Cargo.toml b/crates/rattler_conda_types/Cargo.toml
index 529082e4b..a9955697b 100644
--- a/crates/rattler_conda_types/Cargo.toml
+++ b/crates/rattler_conda_types/Cargo.toml
@@ -38,10 +38,11 @@ typed-path = { workspace = true }
 url = { workspace = true, features = ["serde"] }
 indexmap = { workspace = true }
 rattler_redaction = { version = "0.1.0", path = "../rattler_redaction" }
+dirs = { workspace = true }
 
 [dev-dependencies]
 rand = { workspace = true }
-insta = { workspace = true, features = ["yaml", "redactions", "toml", "glob"] }
+insta = { workspace = true, features = ["yaml", "redactions", "toml", "glob", "filters"] }
 rattler_package_streaming = { path = "../rattler_package_streaming", default-features = false, features = ["rustls-tls"] }
 tempfile = { workspace = true }
 rstest = { workspace = true }
diff --git a/crates/rattler_conda_types/src/channel/mod.rs b/crates/rattler_conda_types/src/channel/mod.rs
index 8dc345008..fd62f23a2 100644
--- a/crates/rattler_conda_types/src/channel/mod.rs
+++ b/crates/rattler_conda_types/src/channel/mod.rs
@@ -6,13 +6,12 @@ use std::{
 };
 
 use file_url::directory_path_to_url;
+use rattler_redaction::Redact;
 use serde::{Deserialize, Serialize, Serializer};
 use thiserror::Error;
 use typed_path::{Utf8NativePathBuf, Utf8TypedPath, Utf8TypedPathBuf};
 use url::Url;
 
-use rattler_redaction::Redact;
-
 use super::{ParsePlatformError, Platform};
 use crate::utils::{
     path::is_path,
@@ -449,6 +448,17 @@ fn absolute_path(path: &str, root_dir: &Path) -> Result<Utf8TypedPathBuf, ParseC
         return Ok(path.normalize());
     }
 
+    // Parse the `~` as the home folder
+    if path.starts_with("~/") {
+        return Ok(Utf8TypedPathBuf::from(
+            dirs::home_dir()
+                .ok_or(ParseChannelError::InvalidPath(path.to_string()))?
+                .to_string_lossy()
+                .as_ref(),
+        )
+        .join(path.strip_prefix("~/").unwrap()));
+    }
+
     let root_dir_str = root_dir
         .to_str()
         .ok_or_else(|| ParseChannelError::NotUtf8RootDir(root_dir.to_path_buf()))?;
@@ -665,18 +675,6 @@ mod tests {
         assert_eq!(channel.name.as_deref(), Some("conda-forge/label/rust_dev"));
     }
 
-    #[test]
-    fn test_is_path() {
-        assert!(is_path("./foo"));
-        assert!(is_path("/foo"));
-        assert!(is_path("~/foo"));
-        assert!(is_path("../foo"));
-        assert!(is_path("/C:/foo"));
-        assert!(is_path("C:/foo"));
-
-        assert!(!is_path("conda-forge/label/rust_dev"));
-    }
-
     #[test]
     fn channel_canonical_name() {
         let config = ChannelConfig::default_with_root_dir(std::env::current_dir().unwrap());
diff --git a/crates/rattler_conda_types/src/match_spec/parse.rs b/crates/rattler_conda_types/src/match_spec/parse.rs
index 1fdaccf1c..d42f69494 100644
--- a/crates/rattler_conda_types/src/match_spec/parse.rs
+++ b/crates/rattler_conda_types/src/match_spec/parse.rs
@@ -23,7 +23,7 @@ use super::{
 use crate::{
     build_spec::{BuildNumberSpec, ParseBuildNumberSpecError},
     package::ArchiveIdentifier,
-    utils::{path::is_path, url::parse_scheme},
+    utils::{path::is_absolute_path, url::parse_scheme},
     version_spec::{
         is_start_of_version_constraint,
         version_tree::{recognize_constraint, recognize_version},
@@ -251,8 +251,8 @@ fn parse_bracket_vec_into_components(
                 let url = if parse_scheme(value).is_some() {
                     Url::parse(value)?
                 }
-                // 2 Is the spec a path, parse it as an url
-                else if is_path(value) {
+                // 2 Is the spec an absolute path, parse it as an url
+                else if is_absolute_path(value) {
                     let path = Utf8TypedPath::from(value);
                     file_url::file_path_to_url(path)
                         .map_err(|_error| ParseMatchSpecError::InvalidPackagePathOrUrl)?
@@ -286,7 +286,7 @@ pub fn parse_url_like(input: &str) -> Result<Option<Url>, ParseMatchSpecError> {
             .map_err(ParseMatchSpecError::from);
     }
     // Is the spec a path, parse it as an url
-    if is_path(input) {
+    if is_absolute_path(input) {
         let path = Utf8TypedPath::from(input);
         return file_url::file_path_to_url(path)
             .map(Some)
@@ -967,6 +967,8 @@ mod tests {
             "conda-forge/linux-32::python[version=3.9, subdir=linux-64]",
             "conda-forge/linux-32::python ==3.9[subdir=linux-64, build_number=\"0\"]",
             "rust ~=1.2.3",
+            "~/channel/dir::package",
+            "./relative/channel::package",
         ];
 
         let evaluated: IndexMap<_, _> = specs
@@ -983,7 +985,17 @@ mod tests {
                 )
             })
             .collect();
-        insta::assert_yaml_snapshot!(format!("test_from_string_{strictness:?}"), evaluated);
+
+        // Strip absolute paths from the channels for testing
+        let path = Url::from_directory_path(dirs::home_dir().unwrap()).unwrap();
+        insta::with_settings!({filters => vec![
+            (path.as_str(), "file://<ROOT>/"),
+        ]}, {
+            insta::assert_yaml_snapshot!(
+            format!("test_from_string_{strictness:?}"),
+            evaluated
+        );
+        });
     }
 
     #[rstest]
@@ -1168,9 +1180,6 @@ mod tests {
         let err = MatchSpec::from_str("bla/bla", Strict)
             .expect_err("Should try to parse as name not url");
         assert_eq!(err.to_string(), "'bla/bla' is not a valid package name. Package names can only contain 0-9, a-z, A-Z, -, _, or .");
-
-        let err = MatchSpec::from_str("./test/file", Strict).expect_err("Invalid url");
-        assert_eq!(err.to_string(), "invalid package path or url");
     }
 
     #[test]
diff --git a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap
index f12b4a71e..5f1495337 100644
--- a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap
+++ b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap
@@ -101,3 +101,13 @@ python=*:
 rust ~=1.2.3:
   name: rust
   version: ~=1.2.3
+"~/channel/dir::package":
+  name: package
+  channel:
+    base_url: "file://<ROOT>/channel/dir/"
+    name: ~/channel/dir
+"./relative/channel::package":
+  name: package
+  channel:
+    base_url: "file://<ROOT>/dev/rattler/crates/rattler_conda_types/relative/channel/"
+    name: "./relative/channel"
diff --git a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap
index 6d6256054..75f593ec6 100644
--- a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap
+++ b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap
@@ -93,3 +93,13 @@ python=*:
 rust ~=1.2.3:
   name: rust
   version: ~=1.2.3
+"~/channel/dir::package":
+  name: package
+  channel:
+    base_url: "file://<ROOT>/channel/dir/"
+    name: ~/channel/dir
+"./relative/channel::package":
+  name: package
+  channel:
+    base_url: "file://<ROOT>/dev/rattler/crates/rattler_conda_types/relative/channel/"
+    name: "./relative/channel"
diff --git a/crates/rattler_conda_types/src/utils/path.rs b/crates/rattler_conda_types/src/utils/path.rs
index b88735ef5..e9c240d35 100644
--- a/crates/rattler_conda_types/src/utils/path.rs
+++ b/crates/rattler_conda_types/src/utils/path.rs
@@ -1,5 +1,21 @@
 use itertools::Itertools;
 
+/// Returns true if the specified string is considered to be an absolute path
+pub(crate) fn is_absolute_path(path: &str) -> bool {
+    if path.contains("://") {
+        return false;
+    }
+
+    // Check if the path starts with a common absolute path prefix
+    if path.starts_with('/') || path.starts_with("\\\\") {
+        return true;
+    }
+
+    // A drive letter followed by a colon and a (backward or forward) slash
+    matches!(path.chars().take(3).collect_tuple(),
+        Some((letter, ':', '/' | '\\')) if letter.is_alphabetic())
+}
+
 /// Returns true if the specified string is considered to be a path
 pub(crate) fn is_path(path: &str) -> bool {
     if path.contains("://") {
@@ -21,3 +37,36 @@ pub(crate) fn is_path(path: &str) -> bool {
     matches!(path.chars().take(3).collect_tuple(),
         Some((letter, ':', '/' | '\\')) if letter.is_alphabetic())
 }
+
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_is_absolute_path() {
+        assert!(is_absolute_path("/foo"));
+        assert!(is_absolute_path("/C:/foo"));
+        assert!(is_absolute_path("C:/foo"));
+        assert!(is_absolute_path("\\\\foo"));
+        assert!(is_absolute_path("\\\\server\\foo"));
+
+        assert!(!is_absolute_path("conda-forge/label/rust_dev"));
+        assert!(!is_absolute_path("~/foo"));
+        assert!(!is_absolute_path("./foo"));
+        assert!(!is_absolute_path("../foo"));
+        assert!(!is_absolute_path("foo"));
+    }
+
+    #[test]
+    fn test_is_path() {
+        assert!(is_path("/foo"));
+        assert!(is_path("/C:/foo"));
+        assert!(is_path("C:/foo"));
+        assert!(is_path("\\\\foo"));
+        assert!(is_path("\\\\server\\foo"));
+
+        assert!(is_path("./conda-forge/label/rust_dev"));
+        assert!(is_path("~/foo"));
+        assert!(is_path("./foo"));
+        assert!(is_path("../foo"));
+    }
+}

From bcabbfd597d84dd93e6300bb3aab04aa0e7ac1f2 Mon Sep 17 00:00:00 2001
From: Ruben Arts <ruben.arts@hotmail.com>
Date: Tue, 6 Aug 2024 11:28:30 +0200
Subject: [PATCH 05/17] test: cleanup subdir test

---
 .../src/match_spec/parse.rs                   | 61 ++++++++-----------
 1 file changed, 27 insertions(+), 34 deletions(-)

diff --git a/crates/rattler_conda_types/src/match_spec/parse.rs b/crates/rattler_conda_types/src/match_spec/parse.rs
index d42f69494..08415c58c 100644
--- a/crates/rattler_conda_types/src/match_spec/parse.rs
+++ b/crates/rattler_conda_types/src/match_spec/parse.rs
@@ -1204,40 +1204,33 @@ mod tests {
 
     #[test]
     fn test_parse_channel_subdir() {
-        let (channel, subdir) = parse_channel_and_subdir("conda-forge").unwrap();
-        assert_eq!(
-            channel.unwrap(),
-            Channel::from_str("conda-forge", &channel_config()).unwrap()
-        );
-        assert_eq!(subdir, None);
-
-        let (channel, subdir) = parse_channel_and_subdir("conda-forge/linux-64").unwrap();
-        assert_eq!(
-            channel.unwrap(),
-            Channel::from_str("conda-forge", &channel_config()).unwrap()
-        );
-        assert_eq!(subdir, Some("linux-64".to_string()));
-
-        let (channel, subdir) = parse_channel_and_subdir("conda-forge/label/test").unwrap();
-        assert_eq!(
-            channel.unwrap(),
-            Channel::from_str("conda-forge/label/test", &channel_config()).unwrap()
-        );
-        assert_eq!(subdir, None);
-
-        let (channel, subdir) =
-            parse_channel_and_subdir("conda-forge/linux-64/label/test").unwrap();
-        assert_eq!(
-            channel.unwrap(),
-            Channel::from_str("conda-forge/linux-64/label/test", &channel_config()).unwrap()
-        );
-        assert_eq!(subdir, None);
+        let test_cases = vec![
+            ("conda-forge", Some("conda-forge"), None),
+            (
+                "conda-forge/linux-64",
+                Some("conda-forge"),
+                Some("linux-64"),
+            ),
+            (
+                "conda-forge/label/test",
+                Some("conda-forge/label/test"),
+                None,
+            ),
+            (
+                "conda-forge/linux-64/label/test",
+                Some("conda-forge/linux-64/label/test"),
+                None,
+            ),
+            ("*/linux-64", Some("*"), Some("linux-64")),
+        ];
 
-        let (channel, subdir) = parse_channel_and_subdir("*/linux-64").unwrap();
-        assert_eq!(
-            channel.unwrap(),
-            Channel::from_str("*", &channel_config()).unwrap()
-        );
-        assert_eq!(subdir, Some("linux-64".to_string()));
+        for (input, expected_channel, expected_subdir) in test_cases {
+            let (channel, subdir) = parse_channel_and_subdir(input).unwrap();
+            assert_eq!(
+                channel.unwrap(),
+                Channel::from_str(expected_channel.unwrap(), &channel_config()).unwrap()
+            );
+            assert_eq!(subdir, expected_subdir.map(|s| s.to_string()));
+        }
     }
 }

From 6e5f1339e11771a6258598be68102bff7e710ee0 Mon Sep 17 00:00:00 2001
From: Ruben Arts <ruben.arts@hotmail.com>
Date: Tue, 6 Aug 2024 11:32:01 +0200
Subject: [PATCH 06/17] clippy

---
 crates/rattler_conda_types/src/utils/path.rs | 2 --
 1 file changed, 2 deletions(-)

diff --git a/crates/rattler_conda_types/src/utils/path.rs b/crates/rattler_conda_types/src/utils/path.rs
index e9c240d35..0ab8b46fe 100644
--- a/crates/rattler_conda_types/src/utils/path.rs
+++ b/crates/rattler_conda_types/src/utils/path.rs
@@ -39,8 +39,6 @@ pub(crate) fn is_path(path: &str) -> bool {
 }
 
 mod tests {
-    use super::*;
-
     #[test]
     fn test_is_absolute_path() {
         assert!(is_absolute_path("/foo"));

From de291046c9820f80ede43cc2e8a091a391fb4e6d Mon Sep 17 00:00:00 2001
From: Ruben Arts <ruben@prefix.dev>
Date: Tue, 6 Aug 2024 11:42:06 +0200
Subject: [PATCH 07/17] Update crates/rattler_conda_types/src/channel/mod.rs

Co-authored-by: Bas Zalmstra <zalmstra.bas@gmail.com>
---
 crates/rattler_conda_types/src/channel/mod.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/crates/rattler_conda_types/src/channel/mod.rs b/crates/rattler_conda_types/src/channel/mod.rs
index fd62f23a2..8b513337d 100644
--- a/crates/rattler_conda_types/src/channel/mod.rs
+++ b/crates/rattler_conda_types/src/channel/mod.rs
@@ -449,14 +449,14 @@ fn absolute_path(path: &str, root_dir: &Path) -> Result<Utf8TypedPathBuf, ParseC
     }
 
     // Parse the `~` as the home folder
-    if path.starts_with("~/") {
+    if let Some(user_path) = path.strip_prefix("~/").ok().or_else(|| path.strip_prefix("~\\").ok()) {
         return Ok(Utf8TypedPathBuf::from(
             dirs::home_dir()
                 .ok_or(ParseChannelError::InvalidPath(path.to_string()))?
                 .to_string_lossy()
                 .as_ref(),
         )
-        .join(path.strip_prefix("~/").unwrap()));
+        .join(user_path);
     }
 
     let root_dir_str = root_dir

From 0bcd25a972049665e0654a1edfeebccbb0f22fd5 Mon Sep 17 00:00:00 2001
From: Ruben Arts <ruben@prefix.dev>
Date: Tue, 6 Aug 2024 11:42:21 +0200
Subject: [PATCH 08/17] Update crates/rattler_conda_types/src/utils/path.rs

Co-authored-by: Bas Zalmstra <zalmstra.bas@gmail.com>
---
 crates/rattler_conda_types/src/utils/path.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/crates/rattler_conda_types/src/utils/path.rs b/crates/rattler_conda_types/src/utils/path.rs
index 0ab8b46fe..f78f56517 100644
--- a/crates/rattler_conda_types/src/utils/path.rs
+++ b/crates/rattler_conda_types/src/utils/path.rs
@@ -26,6 +26,7 @@ pub(crate) fn is_path(path: &str) -> bool {
     if path.starts_with("./")
         || path.starts_with("..")
         || path.starts_with("~/")
+        || path.starts_with("~\\")
         || path.starts_with('/')
         || path.starts_with("\\\\")
         || path.starts_with("//")

From 91439c8397d5281de27400460fa7b2db18bd9bea Mon Sep 17 00:00:00 2001
From: Ruben Arts <ruben@prefix.dev>
Date: Tue, 6 Aug 2024 11:42:26 +0200
Subject: [PATCH 09/17] Update crates/rattler_conda_types/src/utils/path.rs

Co-authored-by: Bas Zalmstra <zalmstra.bas@gmail.com>
---
 crates/rattler_conda_types/src/utils/path.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/crates/rattler_conda_types/src/utils/path.rs b/crates/rattler_conda_types/src/utils/path.rs
index f78f56517..fb07597e9 100644
--- a/crates/rattler_conda_types/src/utils/path.rs
+++ b/crates/rattler_conda_types/src/utils/path.rs
@@ -65,6 +65,7 @@ mod tests {
 
         assert!(is_path("./conda-forge/label/rust_dev"));
         assert!(is_path("~/foo"));
+        assert!(is_path("~\\foo"));
         assert!(is_path("./foo"));
         assert!(is_path("../foo"));
     }

From 37d12c23ee7248a1458a22908fa4b5c5439ff6f2 Mon Sep 17 00:00:00 2001
From: Ruben Arts <ruben@prefix.dev>
Date: Tue, 6 Aug 2024 11:42:31 +0200
Subject: [PATCH 10/17] Update crates/rattler_conda_types/src/utils/path.rs

Co-authored-by: Bas Zalmstra <zalmstra.bas@gmail.com>
---
 crates/rattler_conda_types/src/utils/path.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/crates/rattler_conda_types/src/utils/path.rs b/crates/rattler_conda_types/src/utils/path.rs
index fb07597e9..b6f5697ac 100644
--- a/crates/rattler_conda_types/src/utils/path.rs
+++ b/crates/rattler_conda_types/src/utils/path.rs
@@ -50,6 +50,7 @@ mod tests {
 
         assert!(!is_absolute_path("conda-forge/label/rust_dev"));
         assert!(!is_absolute_path("~/foo"));
+        assert!(!is_absolute_path("~\\foo"));
         assert!(!is_absolute_path("./foo"));
         assert!(!is_absolute_path("../foo"));
         assert!(!is_absolute_path("foo"));

From 136ab666083ec6c5dadd1b357daa1319c8c4932e Mon Sep 17 00:00:00 2001
From: Ruben Arts <ruben.arts@hotmail.com>
Date: Tue, 6 Aug 2024 12:39:53 +0200
Subject: [PATCH 11/17] fix: don't parse windows ~\ paths

---
 crates/rattler_conda_types/src/channel/mod.rs    | 16 +++++++++++++---
 .../rattler_conda_types/src/match_spec/parse.rs  |  1 +
 ...__parse__tests__test_from_string_Lenient.snap |  2 ++
 ...c__parse__tests__test_from_string_Strict.snap |  2 ++
 crates/rattler_conda_types/src/utils/path.rs     |  8 +++++---
 5 files changed, 23 insertions(+), 6 deletions(-)

diff --git a/crates/rattler_conda_types/src/channel/mod.rs b/crates/rattler_conda_types/src/channel/mod.rs
index 8b513337d..1ff8d555e 100644
--- a/crates/rattler_conda_types/src/channel/mod.rs
+++ b/crates/rattler_conda_types/src/channel/mod.rs
@@ -443,20 +443,26 @@ pub(crate) const fn default_platforms() -> &'static [Platform] {
 
 /// Returns the specified path as an absolute path
 fn absolute_path(path: &str, root_dir: &Path) -> Result<Utf8TypedPathBuf, ParseChannelError> {
+    // Non parsable path
+    if path.starts_with("~\\") {
+        return Err(ParseChannelError::InvalidPath(path.to_owned()));
+    }
+
     let path = Utf8TypedPath::from(path);
     if path.is_absolute() {
         return Ok(path.normalize());
     }
 
-    // Parse the `~` as the home folder
-    if let Some(user_path) = path.strip_prefix("~/").ok().or_else(|| path.strip_prefix("~\\").ok()) {
+    // Parse the `~/` as the home folder
+    if let Ok(user_path) = path.strip_prefix("~/")
+    {
         return Ok(Utf8TypedPathBuf::from(
             dirs::home_dir()
                 .ok_or(ParseChannelError::InvalidPath(path.to_string()))?
                 .to_string_lossy()
                 .as_ref(),
         )
-        .join(user_path);
+        .join(user_path));
     }
 
     let root_dir_str = root_dir
@@ -525,6 +531,10 @@ mod tests {
             absolute_path("../foo", &current_dir).as_ref(),
             Ok(&parent_dir.join("foo"))
         );
+
+        let binding = dirs::home_dir().unwrap();
+        let home_dir = binding.to_str().unwrap();
+        assert_eq!(absolute_path("~/unix_dir", &current_dir).unwrap().as_str(), format!("{home_dir}/unix_dir").as_str());
     }
 
     #[test]
diff --git a/crates/rattler_conda_types/src/match_spec/parse.rs b/crates/rattler_conda_types/src/match_spec/parse.rs
index 08415c58c..0328af977 100644
--- a/crates/rattler_conda_types/src/match_spec/parse.rs
+++ b/crates/rattler_conda_types/src/match_spec/parse.rs
@@ -968,6 +968,7 @@ mod tests {
             "conda-forge/linux-32::python ==3.9[subdir=linux-64, build_number=\"0\"]",
             "rust ~=1.2.3",
             "~/channel/dir::package",
+            "~\\windows_channel::package",
             "./relative/channel::package",
         ];
 
diff --git a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap
index 5f1495337..b8c7a41d6 100644
--- a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap
+++ b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap
@@ -106,6 +106,8 @@ rust ~=1.2.3:
   channel:
     base_url: "file://<ROOT>/channel/dir/"
     name: ~/channel/dir
+"~\\windows_channel::package":
+  error: invalid channel
 "./relative/channel::package":
   name: package
   channel:
diff --git a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap
index 75f593ec6..7743bcafb 100644
--- a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap
+++ b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap
@@ -98,6 +98,8 @@ rust ~=1.2.3:
   channel:
     base_url: "file://<ROOT>/channel/dir/"
     name: ~/channel/dir
+"~\\windows_channel::package":
+  error: invalid channel
 "./relative/channel::package":
   name: package
   channel:
diff --git a/crates/rattler_conda_types/src/utils/path.rs b/crates/rattler_conda_types/src/utils/path.rs
index b6f5697ac..12b038615 100644
--- a/crates/rattler_conda_types/src/utils/path.rs
+++ b/crates/rattler_conda_types/src/utils/path.rs
@@ -26,7 +26,6 @@ pub(crate) fn is_path(path: &str) -> bool {
     if path.starts_with("./")
         || path.starts_with("..")
         || path.starts_with("~/")
-        || path.starts_with("~\\")
         || path.starts_with('/')
         || path.starts_with("\\\\")
         || path.starts_with("//")
@@ -42,6 +41,7 @@ pub(crate) fn is_path(path: &str) -> bool {
 mod tests {
     #[test]
     fn test_is_absolute_path() {
+        use super::is_absolute_path;
         assert!(is_absolute_path("/foo"));
         assert!(is_absolute_path("/C:/foo"));
         assert!(is_absolute_path("C:/foo"));
@@ -50,14 +50,15 @@ mod tests {
 
         assert!(!is_absolute_path("conda-forge/label/rust_dev"));
         assert!(!is_absolute_path("~/foo"));
-        assert!(!is_absolute_path("~\\foo"));
         assert!(!is_absolute_path("./foo"));
         assert!(!is_absolute_path("../foo"));
         assert!(!is_absolute_path("foo"));
+        assert!(!is_absolute_path("~\\foo"));
     }
 
     #[test]
     fn test_is_path() {
+        use super::is_path;
         assert!(is_path("/foo"));
         assert!(is_path("/C:/foo"));
         assert!(is_path("C:/foo"));
@@ -66,8 +67,9 @@ mod tests {
 
         assert!(is_path("./conda-forge/label/rust_dev"));
         assert!(is_path("~/foo"));
-        assert!(is_path("~\\foo"));
         assert!(is_path("./foo"));
         assert!(is_path("../foo"));
+
+        assert!(!is_path("~\\foo"));
     }
 }

From 24dc401596bc565668a369bc6a10984ba005bd74 Mon Sep 17 00:00:00 2001
From: Ruben Arts <ruben.arts@hotmail.com>
Date: Tue, 6 Aug 2024 12:41:18 +0200
Subject: [PATCH 12/17] fmt

---
 crates/rattler_conda_types/src/channel/mod.rs | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/crates/rattler_conda_types/src/channel/mod.rs b/crates/rattler_conda_types/src/channel/mod.rs
index 1ff8d555e..fc22b5b9e 100644
--- a/crates/rattler_conda_types/src/channel/mod.rs
+++ b/crates/rattler_conda_types/src/channel/mod.rs
@@ -454,8 +454,7 @@ fn absolute_path(path: &str, root_dir: &Path) -> Result<Utf8TypedPathBuf, ParseC
     }
 
     // Parse the `~/` as the home folder
-    if let Ok(user_path) = path.strip_prefix("~/")
-    {
+    if let Ok(user_path) = path.strip_prefix("~/") {
         return Ok(Utf8TypedPathBuf::from(
             dirs::home_dir()
                 .ok_or(ParseChannelError::InvalidPath(path.to_string()))?
@@ -534,7 +533,10 @@ mod tests {
 
         let binding = dirs::home_dir().unwrap();
         let home_dir = binding.to_str().unwrap();
-        assert_eq!(absolute_path("~/unix_dir", &current_dir).unwrap().as_str(), format!("{home_dir}/unix_dir").as_str());
+        assert_eq!(
+            absolute_path("~/unix_dir", &current_dir).unwrap().as_str(),
+            format!("{home_dir}/unix_dir").as_str()
+        );
     }
 
     #[test]

From 443c534d630e965aaf7c69bcaeaa75b0e690b1a0 Mon Sep 17 00:00:00 2001
From: Ruben Arts <ruben.arts@hotmail.com>
Date: Tue, 6 Aug 2024 13:59:31 +0200
Subject: [PATCH 13/17] fix: cleanup channel absolute path

---
 crates/rattler_conda_types/src/channel/mod.rs | 26 ++++++++++---------
 1 file changed, 14 insertions(+), 12 deletions(-)

diff --git a/crates/rattler_conda_types/src/channel/mod.rs b/crates/rattler_conda_types/src/channel/mod.rs
index fc22b5b9e..844627dc3 100644
--- a/crates/rattler_conda_types/src/channel/mod.rs
+++ b/crates/rattler_conda_types/src/channel/mod.rs
@@ -390,11 +390,11 @@ pub enum ParseChannelError {
     InvalidName(String),
 
     /// The root directory is not an absolute path
-    #[error("root directory from channel config is not an absolute path")]
+    #[error("root directory: '{0}' from channel config is not an absolute path")]
     NonAbsoluteRootDir(PathBuf),
 
     /// The root directory is not UTF-8 encoded.
-    #[error("root directory of channel config is not utf8 encoded")]
+    #[error("root directory: '{0}' of channel config is not utf8 encoded")]
     NotUtf8RootDir(PathBuf),
 }
 
@@ -442,13 +442,8 @@ pub(crate) const fn default_platforms() -> &'static [Platform] {
 }
 
 /// Returns the specified path as an absolute path
-fn absolute_path(path: &str, root_dir: &Path) -> Result<Utf8TypedPathBuf, ParseChannelError> {
-    // Non parsable path
-    if path.starts_with("~\\") {
-        return Err(ParseChannelError::InvalidPath(path.to_owned()));
-    }
-
-    let path = Utf8TypedPath::from(path);
+fn absolute_path(path_str: &str, root_dir: &Path) -> Result<Utf8TypedPathBuf, ParseChannelError> {
+    let path = Utf8TypedPath::from(path_str);
     if path.is_absolute() {
         return Ok(path.normalize());
     }
@@ -458,10 +453,11 @@ fn absolute_path(path: &str, root_dir: &Path) -> Result<Utf8TypedPathBuf, ParseC
         return Ok(Utf8TypedPathBuf::from(
             dirs::home_dir()
                 .ok_or(ParseChannelError::InvalidPath(path.to_string()))?
-                .to_string_lossy()
-                .as_ref(),
+                .to_str()
+                .ok_or(ParseChannelError::NotUtf8RootDir(PathBuf::from(path_str)))?,
         )
-        .join(user_path));
+        .join(user_path)
+        .normalize());
     }
 
     let root_dir_str = root_dir
@@ -537,6 +533,12 @@ mod tests {
             absolute_path("~/unix_dir", &current_dir).unwrap().as_str(),
             format!("{home_dir}/unix_dir").as_str()
         );
+        assert_eq!(
+            absolute_path("~/unix_dir/test/../test2", &current_dir)
+                .unwrap()
+                .as_str(),
+            format!("{home_dir}/unix_dir/test2").as_str()
+        );
     }
 
     #[test]

From 126937c2f07ecd5666397980da48ba3ca1b74f00 Mon Sep 17 00:00:00 2001
From: Ruben Arts <ruben.arts@hotmail.com>
Date: Tue, 6 Aug 2024 14:12:10 +0200
Subject: [PATCH 14/17] fix: insta filters

---
 crates/rattler_conda_types/src/match_spec/parse.rs       | 9 ++++++---
 ...tch_spec__parse__tests__test_from_string_Lenient.snap | 4 ++--
 ...atch_spec__parse__tests__test_from_string_Strict.snap | 4 ++--
 3 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/crates/rattler_conda_types/src/match_spec/parse.rs b/crates/rattler_conda_types/src/match_spec/parse.rs
index 0328af977..a4bb68e2a 100644
--- a/crates/rattler_conda_types/src/match_spec/parse.rs
+++ b/crates/rattler_conda_types/src/match_spec/parse.rs
@@ -987,10 +987,13 @@ mod tests {
             })
             .collect();
 
-        // Strip absolute paths from the channels for testing
-        let path = Url::from_directory_path(dirs::home_dir().unwrap()).unwrap();
+        // Strip absolute paths to this crate from the channels for testing
+        let crate_root = env!("CARGO_MANIFEST_DIR");
+        let crate_path = Url::from_directory_path(std::path::Path::new(crate_root)).unwrap();
+        let home = Url::from_directory_path(dirs::home_dir().unwrap()).unwrap();
         insta::with_settings!({filters => vec![
-            (path.as_str(), "file://<ROOT>/"),
+            (home.as_str(), "file://<HOME>/"),
+            (crate_path.as_str(), "file://<CRATE>/"),
         ]}, {
             insta::assert_yaml_snapshot!(
             format!("test_from_string_{strictness:?}"),
diff --git a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap
index b8c7a41d6..6740778af 100644
--- a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap
+++ b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap
@@ -104,12 +104,12 @@ rust ~=1.2.3:
 "~/channel/dir::package":
   name: package
   channel:
-    base_url: "file://<ROOT>/channel/dir/"
+    base_url: "file://<HOME>/channel/dir/"
     name: ~/channel/dir
 "~\\windows_channel::package":
   error: invalid channel
 "./relative/channel::package":
   name: package
   channel:
-    base_url: "file://<ROOT>/dev/rattler/crates/rattler_conda_types/relative/channel/"
+    base_url: "file://<HOME>/dev/rattler/crates/rattler_conda_types/relative/channel/"
     name: "./relative/channel"
diff --git a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap
index 7743bcafb..265df8e7f 100644
--- a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap
+++ b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap
@@ -96,12 +96,12 @@ rust ~=1.2.3:
 "~/channel/dir::package":
   name: package
   channel:
-    base_url: "file://<ROOT>/channel/dir/"
+    base_url: "file://<HOME>/channel/dir/"
     name: ~/channel/dir
 "~\\windows_channel::package":
   error: invalid channel
 "./relative/channel::package":
   name: package
   channel:
-    base_url: "file://<ROOT>/dev/rattler/crates/rattler_conda_types/relative/channel/"
+    base_url: "file://<HOME>/dev/rattler/crates/rattler_conda_types/relative/channel/"
     name: "./relative/channel"

From 838202281febc73f2373eae6072d7b3acb1c7b80 Mon Sep 17 00:00:00 2001
From: Ruben Arts <ruben.arts@hotmail.com>
Date: Tue, 6 Aug 2024 14:32:16 +0200
Subject: [PATCH 15/17] fix: snapshots

---
 crates/rattler_conda_types/src/match_spec/parse.rs              | 2 +-
 ...pes__match_spec__parse__tests__test_from_string_Lenient.snap | 2 +-
 ...ypes__match_spec__parse__tests__test_from_string_Strict.snap | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/crates/rattler_conda_types/src/match_spec/parse.rs b/crates/rattler_conda_types/src/match_spec/parse.rs
index a4bb68e2a..2ddfa6e31 100644
--- a/crates/rattler_conda_types/src/match_spec/parse.rs
+++ b/crates/rattler_conda_types/src/match_spec/parse.rs
@@ -992,8 +992,8 @@ mod tests {
         let crate_path = Url::from_directory_path(std::path::Path::new(crate_root)).unwrap();
         let home = Url::from_directory_path(dirs::home_dir().unwrap()).unwrap();
         insta::with_settings!({filters => vec![
-            (home.as_str(), "file://<HOME>/"),
             (crate_path.as_str(), "file://<CRATE>/"),
+            (home.as_str(), "file://<HOME>/"),
         ]}, {
             insta::assert_yaml_snapshot!(
             format!("test_from_string_{strictness:?}"),
diff --git a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap
index 6740778af..226af1a8c 100644
--- a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap
+++ b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Lenient.snap
@@ -111,5 +111,5 @@ rust ~=1.2.3:
 "./relative/channel::package":
   name: package
   channel:
-    base_url: "file://<HOME>/dev/rattler/crates/rattler_conda_types/relative/channel/"
+    base_url: "file://<CRATE>/relative/channel/"
     name: "./relative/channel"
diff --git a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap
index 265df8e7f..221d2dc94 100644
--- a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap
+++ b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__test_from_string_Strict.snap
@@ -103,5 +103,5 @@ rust ~=1.2.3:
 "./relative/channel::package":
   name: package
   channel:
-    base_url: "file://<HOME>/dev/rattler/crates/rattler_conda_types/relative/channel/"
+    base_url: "file://<CRATE>/relative/channel/"
     name: "./relative/channel"

From fedf49f80413e89fad6bde10c0adbe9fd3ca4300 Mon Sep 17 00:00:00 2001
From: Bas Zalmstra <zalmstra.bas@gmail.com>
Date: Tue, 6 Aug 2024 16:36:59 +0200
Subject: [PATCH 16/17] fix: windows path issue

---
 crates/rattler_conda_types/src/channel/mod.rs | 45 +++++++++++++++----
 py-rattler/Cargo.lock                         |  1 +
 2 files changed, 37 insertions(+), 9 deletions(-)

diff --git a/crates/rattler_conda_types/src/channel/mod.rs b/crates/rattler_conda_types/src/channel/mod.rs
index 844627dc3..f15cb11b8 100644
--- a/crates/rattler_conda_types/src/channel/mod.rs
+++ b/crates/rattler_conda_types/src/channel/mod.rs
@@ -106,7 +106,9 @@ impl NamedChannelOrUrl {
             NamedChannelOrUrl::Name(name) => {
                 let mut base_url = config.channel_alias.clone();
                 if let Ok(mut segments) = base_url.path_segments_mut() {
-                    segments.push(&name);
+                    for segment in name.split(&['/', '\\']) {
+                        segments.push(segment);
+                    }
                 }
                 base_url
             }
@@ -478,6 +480,7 @@ fn absolute_path(path_str: &str, root_dir: &Path) -> Result<Utf8TypedPathBuf, Pa
 mod tests {
     use std::str::FromStr;
 
+    use typed_path::{NativePath, Utf8NativePath};
     use url::Url;
 
     use super::*;
@@ -527,17 +530,20 @@ mod tests {
             Ok(&parent_dir.join("foo"))
         );
 
-        let binding = dirs::home_dir().unwrap();
-        let home_dir = binding.to_str().unwrap();
+        let home_dir = dirs::home_dir()
+            .unwrap()
+            .into_os_string()
+            .into_encoded_bytes();
+        let home_dir = Utf8NativePath::from_bytes_path(&NativePath::new(&home_dir))
+            .unwrap()
+            .to_typed_path();
         assert_eq!(
-            absolute_path("~/unix_dir", &current_dir).unwrap().as_str(),
-            format!("{home_dir}/unix_dir").as_str()
+            absolute_path("~/unix_dir", &current_dir).unwrap(),
+            home_dir.join("unix_dir")
         );
         assert_eq!(
-            absolute_path("~/unix_dir/test/../test2", &current_dir)
-                .unwrap()
-                .as_str(),
-            format!("{home_dir}/unix_dir/test2").as_str()
+            absolute_path("~/unix_dir/test/../test2", &current_dir).unwrap(),
+            home_dir.join("unix_dir").join("test2")
         );
     }
 
@@ -779,4 +785,25 @@ mod tests {
             assert!(!channel.base_url().as_str().ends_with("//"));
         }
     }
+
+    #[test]
+    fn test_compare_channel_and_named_channel_or_url() {
+        let channel_config = ChannelConfig {
+            channel_alias: Url::from_str("https://conda.anaconda.org").unwrap(),
+            root_dir: std::env::current_dir().expect("No current dir set"),
+        };
+        let named = NamedChannelOrUrl::Name("conda-forge".to_string());
+        let channel = Channel::from_str("conda-forge", &channel_config).unwrap();
+        assert_eq!(
+            &channel.base_url,
+            named.into_channel(&channel_config).base_url()
+        );
+
+        let named = NamedChannelOrUrl::Name("nvidia/label/cuda-11.8.0".to_string());
+        let channel = Channel::from_str("nvidia/label/cuda-11.8.0", &channel_config).unwrap();
+        assert_eq!(
+            channel.base_url(),
+            named.into_channel(&channel_config).base_url()
+        );
+    }
 }
diff --git a/py-rattler/Cargo.lock b/py-rattler/Cargo.lock
index 5af18fc7d..47cd605f8 100644
--- a/py-rattler/Cargo.lock
+++ b/py-rattler/Cargo.lock
@@ -2744,6 +2744,7 @@ name = "rattler_conda_types"
 version = "0.27.0"
 dependencies = [
  "chrono",
+ "dirs",
  "file_url",
  "fxhash",
  "glob",

From 90fdc8cd150a39886cc195cff400f47c6f59065d Mon Sep 17 00:00:00 2001
From: Bas Zalmstra <zalmstra.bas@gmail.com>
Date: Tue, 6 Aug 2024 16:52:24 +0200
Subject: [PATCH 17/17] fix clippy

---
 crates/rattler_conda_types/src/channel/mod.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crates/rattler_conda_types/src/channel/mod.rs b/crates/rattler_conda_types/src/channel/mod.rs
index f15cb11b8..221933e43 100644
--- a/crates/rattler_conda_types/src/channel/mod.rs
+++ b/crates/rattler_conda_types/src/channel/mod.rs
@@ -534,7 +534,7 @@ mod tests {
             .unwrap()
             .into_os_string()
             .into_encoded_bytes();
-        let home_dir = Utf8NativePath::from_bytes_path(&NativePath::new(&home_dir))
+        let home_dir = Utf8NativePath::from_bytes_path(NativePath::new(&home_dir))
             .unwrap()
             .to_typed_path();
         assert_eq!(