mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-19 19:44:40 +00:00
Fix symlink preservation in virtual environment creation (#14933)
## Summary
Fixes inconsistent symlink handling in `uv venv` command (#14670).
## Problem
00efde06b6/crates/uv-virtualenv/src/virtualenv.rs (L81)
The original code used `Path::metadata()` which automatically follows
symlinks, causing the system to treat symlinked virtual environment
paths as regular directories. When a user runs uv venv on an existing
symlinked virtual environment `(.venv -> foo)`, the code incorrectly
treats the symlink as a regular directory because `location.metadata()`
automatically follows the symlink and returns metadata for the target
directory `foo/`. This causes the removal logic to delete the symlink
itself and permanently breaking the symlink relationship and replacing
it with a standard directory structure.
## Solution
- Use canonicalize() to resolve symlinks only when removing and
recreating virtual
environments
- This ensures operations target the actual directory while preserving
the symlink
structure
- Minimal change that fixes the core issue without complex path
management
## Test Plan
```bash
➜ test-env alias uv-dev='/Users/wingmunfung/workspace/uv/target/debug/uv'
➜ test-env ln -s dummy foo
➜ test-env ln -s foo .venv
➜ test-env ls -lah
total 0
drwxr-xr-x 4 wingmunfung staff 128B Jul 30 10:39 .
drwxr-xr-x 48 wingmunfung staff 1.5K Jul 29 17:08 ..
lrwxr-xr-x 1 wingmunfung staff 3B Jul 30 10:39 .venv -> foo
lrwxr-xr-x 1 wingmunfung staff 5B Jul 30 10:39 foo -> dummy
➜ test-env uv-dev venv
Using CPython 3.13.2
Creating virtual environment at: .venv
error: Failed to create virtual environment
Caused by: failed to create directory `.venv`: File exists (os error 17)
➜ test-env mkdir dummy
➜ test-env uv-dev venv
Using CPython 3.13.2
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate
➜ test-env ls -lah
total 0
drwxr-xr-x 5 wingmunfung staff 160B Jul 30 10:39 .
drwxr-xr-x 48 wingmunfung staff 1.5K Jul 29 17:08 ..
lrwxr-xr-x 1 wingmunfung staff 3B Jul 30 10:39 .venv -> foo
drwxr-xr-x 7 wingmunfung staff 224B Jul 30 10:39 dummy
lrwxr-xr-x 1 wingmunfung staff 5B Jul 30 10:39 foo -> dummy
➜ test-env uv-dev venv
Using CPython 3.13.2
Creating virtual environment at: .venv
✔ A virtual environment already exists at `.venv`. Do you want to replace it? · yes
Activate with: source .venv/bin/activate
➜ test-env ls -lah
total 0
drwxr-xr-x 5 wingmunfung staff 160B Jul 30 10:39 .
drwxr-xr-x 48 wingmunfung staff 1.5K Jul 29 17:08 ..
lrwxr-xr-x 1 wingmunfung staff 3B Jul 30 10:39 .venv -> foo
drwxr-xr-x@ 7 wingmunfung staff 224B Jul 30 10:39 dummy
lrwxr-xr-x 1 wingmunfung staff 5B Jul 30 10:39 foo -> dummy
### the symlink still exists
```
---------
Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
parent
c4aaae39bc
commit
538ebe6fcf
2 changed files with 200 additions and 4 deletions
|
|
@ -109,15 +109,27 @@ pub(crate) fn create(
|
|||
}
|
||||
OnExisting::Remove => {
|
||||
debug!("Removing existing {name} due to `--clear`");
|
||||
remove_virtualenv(location)?;
|
||||
fs::create_dir_all(location)?;
|
||||
// Before removing the virtual environment, we need to canonicalize the path
|
||||
// because `Path::metadata` will follow the symlink but we're still operating on
|
||||
// the unresolved path and will remove the symlink itself.
|
||||
let location = location
|
||||
.canonicalize()
|
||||
.unwrap_or_else(|_| location.to_path_buf());
|
||||
remove_virtualenv(&location)?;
|
||||
fs::create_dir_all(&location)?;
|
||||
}
|
||||
OnExisting::Fail => {
|
||||
match confirm_clear(location, name)? {
|
||||
Some(true) => {
|
||||
debug!("Removing existing {name} due to confirmation");
|
||||
remove_virtualenv(location)?;
|
||||
fs::create_dir_all(location)?;
|
||||
// Before removing the virtual environment, we need to canonicalize the
|
||||
// path because `Path::metadata` will follow the symlink but we're still
|
||||
// operating on the unresolved path and will remove the symlink itself.
|
||||
let location = location
|
||||
.canonicalize()
|
||||
.unwrap_or_else(|_| location.to_path_buf());
|
||||
remove_virtualenv(&location)?;
|
||||
fs::create_dir_all(&location)?;
|
||||
}
|
||||
Some(false) => {
|
||||
let hint = format!(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue