From db1d34e91bdad1e0d247a1c41cc94c6a116705ae Mon Sep 17 00:00:00 2001 From: twilligon Date: Wed, 29 Oct 2025 09:30:22 -0700 Subject: [PATCH] Support GitHub Gist URLs via HTTP redirects in `uv run` (#16451) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Extend the existing GitHub Gist URL support from #15058 to handle URLs that redirect to Gists. `reqwest` already handled generic URL redirects for us (note this redirects directly to the `.py` on `gist.githubusercontent.com`): ~/git/uv $ uv run https://httpbin.org/redirect-to?url=https://gist.githubusercontent.com/twilligon/4d878a4d9550a4f1df258cde1f058699/raw/c28a4bf0cb6bb9e670cb47c95d28971ffac163e5/hello.py hello world! But running a URL that redirected to a Gist's "main page" (a bit.ly link leading to a Gist, etc.) did not: ~/git/uv $ uv run https://httpbin.org/redirect-to?url=https://gist.github.com/twilligon/4d878a4d9550a4f1df258cde1f058699 File "/tmp/scriptNodt3Q.py", line 87 hello.py ยท GitHub But if we have `reqwest` follow redirects *before* `resolve_gist_url`, we can handle this fine: ~/git/uv $ target/debug/uv run https://httpbin.org/redirect-to?url=https://gist.github.com/twilligon/4d878a4d9550a4f1df258cde1f058699 hello world! ## Test Plan I'd write an automated test but that'd require network access since wiremock doesn't seem to support mocking specific hostnames like `gist.github.com`. As manual tests go, I basically did the above, testing with several redirectors to both generic and Gist URLs. --- crates/uv/src/commands/project/run.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 2e0f2e7e1..9bd590c9b 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -1735,9 +1735,24 @@ impl RunCommand { if !cfg!(unix) || matches!(target_path.try_exists(), Ok(false)) { let mut url = DisplaySafeUrl::parse(&target.to_string_lossy())?; + let client = client_builder.build(); + let mut response = client + .for_host(&url) + .get(Url::from(url.clone())) + .send() + .await?; + // If it's a Gist URL, use the GitHub API to get the raw URL. - if url.host_str() == Some("gist.github.com") { - url = resolve_gist_url(&url, &client_builder).await?; + if response.url().host_str() == Some("gist.github.com") { + url = + resolve_gist_url(DisplaySafeUrl::ref_cast(response.url()), &client_builder) + .await?; + + response = client + .for_host(&url) + .get(Url::from(url.clone())) + .send() + .await?; } let file_stem = url @@ -1750,13 +1765,6 @@ impl RunCommand { .suffix(".py") .tempfile()?; - let client = client_builder.build(); - let response = client - .for_host(&url) - .get(Url::from(url.clone())) - .send() - .await?; - // Stream the response to the file. let mut writer = file.as_file(); let mut reader = response.bytes_stream();