Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ filetime = "0.2.27"
flate2 = { version = "1.1.9", default-features = false, features = ["zlib-rs"] }
futures = { version = "0.3.32", default-features = false, features = ["std", "executor", "async-await"]}
futures-timer = "3.0.3"
git2 = "0.20.4"
git2-curl = "0.21.0"
git2 = { git = "https://github.com/rust-lang/git2-rs.git", rev = "refs/pull/1206/head", features = ["unstable-sha256", "ssh", "https"] }
git2-curl = { git = "https://github.com/rust-lang/git2-rs.git", rev = "refs/pull/1206/head" }
# When updating this, also see if `gix-transport` further down needs updating or some auth-related tests will fail.
gix = { version = "0.81.0", default-features = false, features = ["sha1", "progress-tree", "parallel", "dirwalk", "status"] }
gix-transport = "0.55.1"
Expand All @@ -71,7 +71,7 @@ itertools = "0.14.0"
jiff = { version = "0.2.23", default-features = false, features = [ "std" ] }
jobserver = "0.1.34"
libc = "0.2.184"
libgit2-sys = "0.18.3"
libgit2-sys = { git = "https://github.com/rust-lang/git2-rs.git", rev = "refs/pull/1206/head", features = ["unstable-sha256"] }
libloading = "0.9.0"
memchr = "2.8.0"
memfd = "0.6.5"
Expand Down
26 changes: 26 additions & 0 deletions crates/cargo-test-support/src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,17 @@ pub fn init(path: &Path) -> git2::Repository {
repo
}

/// *(`git2`)* Initialize a new SHA256 repository at the given path.
pub fn init_sha256(path: &Path) -> git2::Repository {
default_search_path();
let mut opts = git2::RepositoryInitOptions::new();
opts.external_template(false)
.object_format(git2::ObjectFormat::Sha256);
let repo = t!(git2::Repository::init_opts(path, &opts));
default_repo_cfg(&repo);
repo
}

fn default_search_path() {
use crate::paths::global_root;
use git2::{ConfigLevel, opts::set_search_path};
Expand Down Expand Up @@ -187,6 +198,21 @@ where
(git_project, repo)
}

/// Create a new [`Project`] in a SHA256 git [`Repository`]
pub fn new_sha256_repo<F>(name: &str, callback: F) -> (Project, git2::Repository)
where
F: FnOnce(ProjectBuilder) -> ProjectBuilder,
{
let mut git_project = project().at(name);
git_project = callback(git_project);
let git_project = git_project.build();

let repo = init_sha256(&git_project.root());
add(&repo);
commit(&repo);
(git_project, repo)
}

/// *(`git2`)* Add all files in the working directory to the git index
pub fn add(repo: &git2::Repository) {
let mut index = t!(repo.index());
Expand Down
7 changes: 6 additions & 1 deletion src/cargo/core/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1028,18 +1028,21 @@ pub struct GitFeatures {
pub shallow_index: bool,
/// When cloning git dependencies, perform a shallow clone and maintain shallowness on subsequent fetches.
pub shallow_deps: bool,
/// Allow SHA256 git repositories.
pub sha256: bool,
}

impl GitFeatures {
pub fn all() -> Self {
GitFeatures {
shallow_index: true,
shallow_deps: true,
sha256: true,
}
}

fn expecting() -> String {
let fields = ["`shallow-index`", "`shallow-deps`"];
let fields = ["`shallow-index`", "`shallow-deps`", "`sha256`"];
format!(
"unstable 'git' only takes {} as valid inputs",
fields.join(" and ")
Expand Down Expand Up @@ -1103,12 +1106,14 @@ fn parse_git(it: impl Iterator<Item = impl AsRef<str>>) -> CargoResult<Option<Gi
let GitFeatures {
shallow_index,
shallow_deps,
sha256,
} = &mut out;

for e in it {
match e.as_ref() {
"shallow-index" => *shallow_index = true,
"shallow-deps" => *shallow_deps = true,
"sha256" => *sha256 = true,
_ => {
bail!(GitFeatures::expecting())
}
Expand Down
109 changes: 93 additions & 16 deletions src/cargo/sources/git/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::sources::IndexSummary;
use crate::sources::RecursivePathSource;
use crate::sources::git::utils::GitDatabase;
use crate::sources::git::utils::GitRemote;
use crate::sources::git::utils::probe_remote_object_format;
use crate::sources::git::utils::rev_to_oid;
use crate::sources::source::MaybePackage;
use crate::sources::source::QueryKind;
Expand Down Expand Up @@ -47,14 +48,17 @@ use url::Url;
/// │ │ └── e33d1ac/
/// │ ├── log-c58e1db3de7c154d-shallow/
/// │ │ └── 11eda98/
/// │ └── foo-9f1e8cfc50c5ba7d-sha256/
/// │ └── cfc50c5/
/// └── db/
/// ├── gimli-a0d193bd15a5ed96/
/// └── log-c58e1db3de7c154d-shallow/
/// ├── log-c58e1db3de7c154d-shallow/
/// └── foo-9f1e8cfc50c5ba7d-sha256/
/// ```
///
/// For more on Git cache directory, see ["Cargo Home"] in The Cargo Book.
///
/// For more on the directory format `<pkg>-<hash>[-shallow]`, see [`ident`]
/// For more on the directory format `<pkg>-<hash>[-shallow][-sha256]`, see [`ident`]
/// and [`ident_shallow`].
///
/// ## Locked to a revision
Expand Down Expand Up @@ -171,23 +175,81 @@ impl<'gctx> GitSource<'gctx> {
}

fn mark_used(&self) -> CargoResult<()> {
let format = match &*self.locked_rev.borrow() {
Revision::Locked(oid) => oid.object_format(),
_ => unreachable!("locked_rev must be resolved before mark_used"),
};
let ident = self.ident_for_format(format);
self.gctx
.deferred_global_last_use()?
.mark_git_checkout_used(global_cache_tracker::GitCheckout {
encoded_git_name: self.ident,
encoded_git_name: ident,
short_name: self.short_id.borrow().expect("update before download"),
size: None,
});
Ok(())
}

fn ident_for_format(&self, format: git2::ObjectFormat) -> InternedString {
match format {
git2::ObjectFormat::Sha1 => self.ident,
git2::ObjectFormat::Sha256 => format!("{}-sha256", self.ident).into(),
}
}

/// Determines the Git object format for this remote.
///
/// This may probe the remote repository if needed.
fn object_format_hint(&self) -> CargoResult<Option<git2::ObjectFormat>> {
if let Revision::Locked(oid) = &*self.locked_rev.borrow() {
return Ok(Some(oid.object_format()));
}

let git_db_path = self.gctx.git_db_path();
self.gctx
.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &git_db_path);
let git_db_path = git_db_path.as_path_unlocked();

for format in [git2::ObjectFormat::Sha1, git2::ObjectFormat::Sha256] {
let ident = self.ident_for_format(format);
let path = git_db_path.join(ident);
if path.exists()
&& let Ok(db) = self.remote.db_at(&path)
{
return Ok(Some(db.object_format()));
}
}

Ok(None)
}

/// Fetch and return a [`GitDatabase`] with the resolved revision
/// for this source,
///
/// This won't fetch anything if the required revision is
/// already available locally.
pub(crate) fn fetch_db(&self, is_submodule: bool) -> CargoResult<(GitDatabase, git2::Oid)> {
let db_path = self.gctx.git_db_path().join(&self.ident);
let mut is_update_status_shown = false;

let format = if let Some(format) = self.object_format_hint()? {
format
} else {
if !is_update_status_shown {
is_update_status_shown = true;
self.show_update_status(is_submodule)?;
}
if let Some(offline_flag) = self.gctx.offline_flag() {
anyhow::bail!(
"can't checkout from '{}': you are in the offline mode ({offline_flag})",
self.remote.url()
);
}

trace!("probing git source `{:?}`", self.remote);
probe_remote_object_format(self.source_id.borrow().url(), self.gctx)?
};

let db_path = self.gctx.git_db_path().join(self.ident_for_format(format));
let db_path = db_path.into_path_unlocked();

let db = self.remote.db_at(&db_path).ok();
Expand Down Expand Up @@ -226,28 +288,41 @@ impl<'gctx> GitSource<'gctx> {
);
}

if !self.quiet {
let scope = if is_submodule {
"submodule"
} else {
"repository"
};
self.gctx
.shell()
.status("Updating", format!("git {scope} `{}`", self.remote.url()))?;
if !is_update_status_shown {
self.show_update_status(is_submodule)?;
}

trace!("updating git source `{:?}`", self.remote);

let locked_rev = locked_rev.clone().into();
let manifest_reference = self.source_id.borrow().git_reference().unwrap();
self.remote
.checkout(&db_path, db, manifest_reference, &locked_rev, self.gctx)?
self.remote.checkout(
&db_path,
db,
manifest_reference,
&locked_rev,
format,
self.gctx,
)?
}
};
Ok((db, actual_rev))
}

fn show_update_status(&self, is_submodule: bool) -> CargoResult<()> {
if self.quiet {
return Ok(());
}
let scope = if is_submodule {
"submodule"
} else {
"repository"
};
self.gctx
.shell()
.status("Updating", format!("git {scope} `{}`", self.remote.url()))
}

fn update(&self) -> CargoResult<()> {
if self.path_source.borrow().is_some() {
self.mark_used()?;
Expand Down Expand Up @@ -282,10 +357,11 @@ impl<'gctx> GitSource<'gctx> {
// 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 ident = self.ident_for_format(actual_rev.object_format());
let checkout_path = self
.gctx
.git_checkouts_path()
.join(&self.ident)
.join(ident)
.join(short_id.as_str());
let checkout_path = checkout_path.into_path_unlocked();
db.copy_to(actual_rev, &checkout_path, self.gctx, self.quiet)?;
Expand Down Expand Up @@ -360,6 +436,7 @@ fn ident(id: &SourceId) -> String {

/// Like [`ident()`], but appends `-shallow` to it, turning
/// `proto://host/path/repo` into `repo-<hash-of-url>-shallow`.
/// SHA256 repositories add the `-sha256` suffix on top of this identifier.
///
/// It's important to separate shallow from non-shallow clones for reasons of
/// backwards compatibility --- older cargo's aren't necessarily handling
Expand Down
Loading