mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-10 10:32:09 +00:00
Add support for Git dependencies (#283)
## Summary
This PR adds support for Git dependencies, like:
```
flask @ git+https://github.com/pallets/flask.git
```
Right now, they're only supported in the resolver (and not the
installer), since the installer doesn't yet support source distributions
at all.
The general approach here is based on Cargo's Git implementation.
Specifically, I adapted Cargo's
[`git`](23eb492cf9/src/cargo/sources/git/mod.rs
)
module to perform the cloning, which is based on `libgit2`.
As compared to Cargo's implementation, I made the following changes:
- Removed any unnecessary code.
- Fixed any Clippy errors for our stricter ruleset.
- Removed the dependency on `curl`, in favor of `reqwest` which we use
elsewhere.
- Removed the ability to use `gix`. Cargo allows the use of `gix` as an
experimental flag, but it only supports a small subset of the
operations. When Cargo fully adopts `gix`, we should plan to do the
same.
- Removed Cargo's host key checking. We need to re-add this! I'll do it
shortly.
- Removed Cargo's progress bars. We should re-add this too, but we use
`indicatif` and Cargo had their own thing.
There are a few follow-ups to consider:
- Adding support in the installer.
- When we lock, we should write out the Git URL that includes the exact
SHA. This lets us cache in perpetuity and avoids dependencies changing
without re-locking.
- When we resolve, we should _always_ try to refresh Git dependencies.
(Right now, we skip if the wheel was already built.)
I'll work on the latter two in follow-up PRs.
Closes #202.
This commit is contained in:
parent
4adaa9a700
commit
62c474d880
15 changed files with 2162 additions and 32 deletions
91
crates/puffin-git/src/source.rs
Normal file
91
crates/puffin-git/src/source.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
//! Git support is derived from Cargo's implementation.
|
||||
//! Cargo is dual-licensed under either Apache 2.0 or MIT, at the user's choice.
|
||||
//! Source: <https://github.com/rust-lang/cargo/blob/23eb492cf920ce051abfc56bbaf838514dc8365c/src/cargo/sources/git/source.rs>
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use reqwest::Client;
|
||||
use tracing::debug;
|
||||
|
||||
use puffin_cache::{digest, CanonicalUrl};
|
||||
|
||||
use crate::git::GitRemote;
|
||||
use crate::{FetchStrategy, Git, GitReference};
|
||||
|
||||
/// A remote Git source that can be checked out locally.
|
||||
pub struct GitSource {
|
||||
/// The git remote which we're going to fetch from.
|
||||
remote: GitRemote,
|
||||
/// The Git reference from the manifest file.
|
||||
manifest_reference: GitReference,
|
||||
/// The revision which a git source is locked to.
|
||||
/// This is expected to be set after the Git repository is fetched.
|
||||
locked_rev: Option<git2::Oid>,
|
||||
/// The identifier of this source for Cargo's Git cache directory.
|
||||
/// See [`ident`] for more.
|
||||
ident: String,
|
||||
/// The HTTP client to use for fetching.
|
||||
client: Client,
|
||||
/// The fetch strategy to use when cloning.
|
||||
strategy: FetchStrategy,
|
||||
/// The path to the Git source database.
|
||||
git: PathBuf,
|
||||
}
|
||||
|
||||
impl GitSource {
|
||||
pub fn new(reference: Git, git: PathBuf) -> Self {
|
||||
Self {
|
||||
remote: GitRemote::new(&reference.url),
|
||||
manifest_reference: reference.reference,
|
||||
locked_rev: reference.precise,
|
||||
ident: digest(&CanonicalUrl::new(&reference.url)),
|
||||
client: Client::new(),
|
||||
strategy: FetchStrategy::Libgit2,
|
||||
git,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fetch(self) -> Result<PathBuf> {
|
||||
// The path to the repo, within the Git database.
|
||||
let db_path = self.git.join("db").join(&self.ident);
|
||||
|
||||
let (db, actual_rev) = match (self.locked_rev, self.remote.db_at(&db_path).ok()) {
|
||||
// If we have a locked revision, and we have a preexisting database
|
||||
// which has that revision, then no update needs to happen.
|
||||
(Some(rev), Some(db)) if db.contains(rev) => (db, rev),
|
||||
|
||||
// ... otherwise we use this state to update the git database. Note
|
||||
// that we still check for being offline here, for example in the
|
||||
// situation that we have a locked revision but the database
|
||||
// doesn't have it.
|
||||
(locked_rev, db) => {
|
||||
debug!("Updating Git source: `{:?}`", self.remote);
|
||||
|
||||
self.remote.checkout(
|
||||
&db_path,
|
||||
db,
|
||||
&self.manifest_reference,
|
||||
locked_rev,
|
||||
self.strategy,
|
||||
&self.client,
|
||||
)?
|
||||
}
|
||||
};
|
||||
|
||||
// Don’t use the full hash, in order to contribute less to reaching the
|
||||
// path length limit on Windows.
|
||||
let short_id = db.to_short_id(actual_rev)?;
|
||||
|
||||
// Check out `actual_rev` from the database to a scoped location on the
|
||||
// filesystem. This will use hard links and such to ideally make the
|
||||
// checkout operation here pretty fast.
|
||||
let checkout_path = self
|
||||
.git
|
||||
.join("checkouts")
|
||||
.join(&self.ident)
|
||||
.join(short_id.as_str());
|
||||
db.copy_to(actual_rev, &checkout_path, self.strategy, &self.client)?;
|
||||
|
||||
Ok(checkout_path)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue