Skip to content

Commit 2f45cb5

Browse files
committed
Assure that git-dependencies will get the correct storage, without ever unshallowing them.
This is an improvement over the previous version which would use unshallowing that effectively makes a shallow repo *not* shallow. Furthermore, we will now only fetch a single commit, each time we fetch, which should be faster for the server as well as for the client. We also make it possible to fetch individual commits that would be specified via Cargo.lock.
1 parent ed55f77 commit 2f45cb5

File tree

6 files changed

+138
-120
lines changed

6 files changed

+138
-120
lines changed

src/cargo/sources/git/mod.rs

Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,56 +14,36 @@ pub mod fetch {
1414
pub enum RemoteKind {
1515
/// A repository belongs to a git dependency.
1616
GitDependency,
17-
/// A repository belongs to a git dependency, and due to usage of checking out specific revisions we can't
18-
/// use shallow clones.
19-
GitDependencyForbidShallow,
2017
/// A repository belongs to a Cargo registry.
2118
Registry,
2219
}
2320

24-
#[derive(Debug, Clone)]
25-
pub enum History {
26-
Shallow(gix::remote::fetch::Shallow),
27-
Unshallow,
28-
}
29-
30-
impl From<History> for gix::remote::fetch::Shallow {
31-
fn from(value: History) -> Self {
32-
match value {
33-
History::Unshallow => gix::remote::fetch::Shallow::undo(),
34-
History::Shallow(how) => how,
35-
}
36-
}
37-
}
38-
3921
impl RemoteKind {
4022
/// Obtain the kind of history we would want for a fetch from our remote knowing if the target repo is already shallow
4123
/// via `repo_is_shallow` along with gitoxide-specific feature configuration via `config`.
42-
pub(crate) fn to_history(&self, repo_is_shallow: bool, config: &Config) -> History {
24+
/// `rev_and_ref` is additional information that affects whether or not we may be shallow.
25+
pub(crate) fn to_shallow_setting(
26+
&self,
27+
repo_is_shallow: bool,
28+
config: &Config,
29+
) -> gix::remote::fetch::Shallow {
4330
let has_feature = |cb: &dyn Fn(GitoxideFeatures) -> bool| {
4431
config
4532
.cli_unstable()
4633
.gitoxide
4734
.map_or(false, |features| cb(features))
4835
};
49-
let how = if repo_is_shallow {
50-
if matches!(self, RemoteKind::GitDependencyForbidShallow) {
51-
return History::Unshallow;
52-
} else {
53-
gix::remote::fetch::Shallow::NoChange
54-
}
55-
} else {
36+
37+
// maintain shallow-ness and keep downloading single commits, or see if we can do shallow clones
38+
if !repo_is_shallow {
5639
match self {
57-
RemoteKind::GitDependency if has_feature(&|git| git.shallow_deps) => {
58-
gix::remote::fetch::Shallow::DepthAtRemote(1.try_into().expect("non-zero"))
59-
}
60-
RemoteKind::Registry if has_feature(&|git| git.shallow_index) => {
61-
gix::remote::fetch::Shallow::DepthAtRemote(1.try_into().expect("non-zero"))
62-
}
63-
_ => gix::remote::fetch::Shallow::NoChange,
40+
RemoteKind::GitDependency if has_feature(&|git| git.shallow_deps) => {}
41+
RemoteKind::Registry if has_feature(&|git| git.shallow_index) => {}
42+
_ => return gix::remote::fetch::Shallow::NoChange,
6443
}
6544
};
66-
History::Shallow(how)
45+
46+
gix::remote::fetch::Shallow::DepthAtRemote(1.try_into().expect("non-zero"))
6747
}
6848
}
6949

src/cargo/sources/git/source.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ impl<'cfg> GitSource<'cfg> {
2929
assert!(source_id.is_git(), "id is not git, id={}", source_id);
3030

3131
let remote = GitRemote::new(source_id.url());
32+
let manifest_reference = source_id.git_reference().unwrap().clone();
33+
let locked_rev =
34+
match source_id.precise() {
35+
Some(s) => Some(git2::Oid::from_str(s).with_context(|| {
36+
format!("precise value for git is not a git revision: {}", s)
37+
})?),
38+
None => None,
39+
};
3240
let ident = ident_shallow(
3341
&source_id,
3442
config
@@ -39,13 +47,8 @@ impl<'cfg> GitSource<'cfg> {
3947

4048
let source = GitSource {
4149
remote,
42-
manifest_reference: source_id.git_reference().unwrap().clone(),
43-
locked_rev: match source_id.precise() {
44-
Some(s) => Some(git2::Oid::from_str(s).with_context(|| {
45-
format!("precise value for git is not a git revision: {}", s)
46-
})?),
47-
None => None,
48-
},
50+
manifest_reference,
51+
locked_rev,
4952
source_id,
5053
path_source: None,
5154
ident,

src/cargo/sources/git/utils.rs

Lines changed: 71 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! authentication/cloning.
33
44
use crate::core::{GitReference, Verbosity};
5-
use crate::sources::git::fetch::{History, RemoteKind};
5+
use crate::sources::git::fetch::RemoteKind;
66
use crate::sources::git::oxide;
77
use crate::sources::git::oxide::cargo_config_to_gitoxide_overrides;
88
use crate::util::errors::CargoResult;
@@ -97,32 +97,25 @@ impl GitRemote {
9797
// if we can. If that can successfully load our revision then we've
9898
// populated the database with the latest version of `reference`, so
9999
// return that database and the rev we resolve to.
100-
let remote_kind = if locked_rev.is_some() || matches!(reference, GitReference::Rev(_)) {
101-
RemoteKind::GitDependencyForbidShallow
102-
} else {
103-
RemoteKind::GitDependency
104-
};
105100
if let Some(mut db) = db {
106-
let history = remote_kind.to_history(db.repo.is_shallow(), cargo_config);
101+
let shallow =
102+
RemoteKind::GitDependency.to_shallow_setting(db.repo.is_shallow(), cargo_config);
107103
fetch(
108104
&mut db.repo,
109105
self.url.as_str(),
110106
reference,
111107
cargo_config,
112-
history,
108+
shallow,
109+
locked_rev,
113110
)
114111
.with_context(|| format!("failed to fetch into: {}", into.display()))?;
115-
match locked_rev {
116-
Some(rev) => {
117-
if db.contains(rev) {
118-
return Ok((db, rev));
119-
}
120-
}
121-
None => {
122-
if let Ok(rev) = reference.resolve(&db.repo) {
123-
return Ok((db, rev));
124-
}
125-
}
112+
113+
let resolved_commit_hash = match locked_rev {
114+
Some(rev) => db.contains(rev).then_some(rev),
115+
None => reference.resolve(&db.repo).ok(),
116+
};
117+
if let Some(rev) = resolved_commit_hash {
118+
return Ok((db, rev));
126119
}
127120
}
128121

@@ -134,13 +127,14 @@ impl GitRemote {
134127
}
135128
paths::create_dir_all(into)?;
136129
let mut repo = init(into, true)?;
137-
let history = remote_kind.to_history(repo.is_shallow(), cargo_config);
130+
let shallow = RemoteKind::GitDependency.to_shallow_setting(repo.is_shallow(), cargo_config);
138131
fetch(
139132
&mut repo,
140133
self.url.as_str(),
141134
reference,
142135
cargo_config,
143-
history,
136+
shallow,
137+
locked_rev,
144138
)
145139
.with_context(|| format!("failed to clone into: {}", into.display()))?;
146140
let rev = match locked_rev {
@@ -462,8 +456,9 @@ impl<'a> GitCheckout<'a> {
462456
cargo_config
463457
.shell()
464458
.status("Updating", format!("git submodule `{}`", url))?;
465-
let history = RemoteKind::GitDependency.to_history(repo.is_shallow(), cargo_config);
466-
fetch(&mut repo, &url, &reference, cargo_config, history).with_context(|| {
459+
let shallow =
460+
RemoteKind::GitDependency.to_shallow_setting(repo.is_shallow(), cargo_config);
461+
fetch(&mut repo, &url, &reference, cargo_config, shallow, None).with_context(|| {
467462
format!(
468463
"failed to fetch submodule `{}` from {}",
469464
child.name().unwrap_or(""),
@@ -842,7 +837,8 @@ pub fn fetch(
842837
orig_url: &str,
843838
reference: &GitReference,
844839
config: &Config,
845-
history: History,
840+
shallow: gix::remote::fetch::Shallow,
841+
locked_rev: Option<git2::Oid>,
846842
) -> CargoResult<()> {
847843
if config.frozen() {
848844
anyhow::bail!(
@@ -855,14 +851,20 @@ pub fn fetch(
855851
}
856852

857853
// If we're fetching from GitHub, attempt GitHub's special fast path for
858-
// testing if we've already got an up-to-date copy of the repository
859-
let oid_to_fetch = match github_fast_path(repo, orig_url, reference, config) {
860-
Ok(FastPathRev::UpToDate) => return Ok(()),
861-
Ok(FastPathRev::NeedsFetch(rev)) => Some(rev),
862-
Ok(FastPathRev::Indeterminate) => None,
863-
Err(e) => {
864-
debug!("failed to check github {:?}", e);
865-
None
854+
// testing if we've already got an up-to-date copy of the repository.
855+
// With shallow histories this is a bit fuzzy, and we opt-out for now.
856+
let is_shallow = repo.is_shallow() || !matches!(shallow, gix::remote::fetch::Shallow::NoChange);
857+
let oid_to_fetch = if is_shallow {
858+
None
859+
} else {
860+
match github_fast_path(repo, orig_url, reference, config) {
861+
Ok(FastPathRev::UpToDate) => return Ok(()),
862+
Ok(FastPathRev::NeedsFetch(rev)) => Some(rev),
863+
Ok(FastPathRev::Indeterminate) => None,
864+
Err(e) => {
865+
debug!("failed to check github {:?}", e);
866+
None
867+
}
866868
}
867869
};
868870

@@ -881,32 +883,44 @@ pub fn fetch(
881883
// The `+` symbol on the refspec means to allow a forced (fast-forward)
882884
// update which is needed if there is ever a force push that requires a
883885
// fast-forward.
884-
match reference {
885-
// For branches and tags we can fetch simply one reference and copy it
886-
// locally, no need to fetch other branches/tags.
887-
GitReference::Branch(b) => {
888-
refspecs.push(format!("+refs/heads/{0}:refs/remotes/origin/{0}", b));
889-
}
890-
GitReference::Tag(t) => {
891-
refspecs.push(format!("+refs/tags/{0}:refs/remotes/origin/tags/{0}", t));
892-
}
893-
894-
GitReference::DefaultBranch => {
895-
refspecs.push(String::from("+HEAD:refs/remotes/origin/HEAD"));
896-
}
886+
if let Some(rev) =
887+
locked_rev.filter(|_| !matches!(shallow, gix::remote::fetch::Shallow::NoChange))
888+
{
889+
// If we want a specific revision and know about, obtain that specifically.
890+
refspecs.push(format!("+{0}:refs/remotes/origin/HEAD", rev));
891+
} else {
892+
match reference {
893+
// For branches and tags we can fetch simply one reference and copy it
894+
// locally, no need to fetch other branches/tags.
895+
GitReference::Branch(b) => {
896+
refspecs.push(format!("+refs/heads/{0}:refs/remotes/origin/{0}", b));
897+
}
898+
GitReference::Tag(t) => {
899+
refspecs.push(format!("+refs/tags/{0}:refs/remotes/origin/tags/{0}", t));
900+
}
897901

898-
GitReference::Rev(rev) => {
899-
if rev.starts_with("refs/") {
900-
refspecs.push(format!("+{0}:{0}", rev));
901-
} else if let Some(oid_to_fetch) = oid_to_fetch {
902-
refspecs.push(format!("+{0}:refs/commit/{0}", oid_to_fetch));
903-
} else {
904-
// We don't know what the rev will point to. To handle this
905-
// situation we fetch all branches and tags, and then we pray
906-
// it's somewhere in there.
907-
refspecs.push(String::from("+refs/heads/*:refs/remotes/origin/*"));
902+
GitReference::DefaultBranch => {
908903
refspecs.push(String::from("+HEAD:refs/remotes/origin/HEAD"));
909-
tags = true;
904+
}
905+
906+
GitReference::Rev(rev) => {
907+
if rev.starts_with("refs/") {
908+
refspecs.push(format!("+{0}:{0}", rev));
909+
} else if let Some(oid_to_fetch) = oid_to_fetch {
910+
refspecs.push(format!("+{0}:refs/commit/{0}", oid_to_fetch));
911+
} else if is_shallow && git2::Oid::from_str(&rev).is_ok() {
912+
// There is a specific commit to fetch and we will just do so in shallow-mode only
913+
// to not disturb the previous logic. Note that with typical settings for shallowing,
914+
// we will just fetch a specific slice of the history.
915+
refspecs.push(format!("+{0}:refs/remotes/origin/HEAD", rev));
916+
} else {
917+
// We don't know what the rev will point to. To handle this
918+
// situation we fetch all branches and tags, and then we pray
919+
// it's somewhere in there.
920+
refspecs.push(String::from("+refs/heads/*:refs/remotes/origin/*"));
921+
refspecs.push(String::from("+HEAD:refs/remotes/origin/HEAD"));
922+
tags = true;
923+
}
910924
}
911925
}
912926
}
@@ -986,7 +1000,7 @@ pub fn fetch(
9861000
);
9871001
let outcome = connection
9881002
.prepare_fetch(&mut progress, gix::remote::ref_map::Options::default())?
989-
.with_shallow(history.clone().into())
1003+
.with_shallow(shallow.clone().into())
9901004
.receive(&mut progress, should_interrupt)?;
9911005
Ok(outcome)
9921006
});

src/cargo/sources/registry/remote.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,13 +305,14 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> {
305305
// checkout.
306306
let url = self.source_id.url();
307307
let repo = self.repo.borrow_mut().unwrap();
308-
let history = RemoteKind::Registry.to_history(repo.is_shallow(), self.config);
308+
let shallow = RemoteKind::Registry.to_shallow_setting(repo.is_shallow(), self.config);
309309
git::fetch(
310310
repo,
311311
url.as_str(),
312312
&self.index_git_ref,
313313
self.config,
314-
history,
314+
shallow,
315+
None,
315316
)
316317
.with_context(|| format!("failed to fetch `{}`", url))?;
317318

tests/testsuite/git.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2221,8 +2221,8 @@ fn gitoxide_clones_registry_with_shallow_protocol_and_follow_up_fetch_maintains_
22212221
.ancestors()
22222222
.all()?
22232223
.count(),
2224-
2,
2225-
"follow-up fetches maintain shallow-ness, whether it's specified or not, keeping shallow boundary where it is"
2224+
1,
2225+
"subsequent shallow fetches wont' fetch what's inbetween, only the single commit that we need while leveraging existing commits"
22262226
);
22272227
assert!(repo.is_shallow());
22282228

@@ -2238,8 +2238,8 @@ fn gitoxide_clones_registry_with_shallow_protocol_and_follow_up_fetch_maintains_
22382238
.ancestors()
22392239
.all()?
22402240
.count(),
2241-
4,
2242-
"shallow boundaries are maintained on subsequent shallow fetches (we don't accidentally unshallow it)"
2241+
1,
2242+
"shallow boundaries are moved with each fetch to maintain only a single commit of history"
22432243
);
22442244
assert!(repo.is_shallow());
22452245

@@ -2358,8 +2358,8 @@ fn gitoxide_clones_registry_without_shallow_protocol_and_follow_up_fetch_uses_sh
23582358
.ancestors()
23592359
.all()?
23602360
.count(),
2361-
3,
2362-
"shallow boundaries are maintained on subsequent shallow fetches (we don't accidentally unshallow it)"
2361+
1,
2362+
"subsequent shallow fetches wont' fetch what's inbetween, only the single commit that we need while leveraging existing commits"
23632363
);
23642364
assert!(shallow_repo.is_shallow());
23652365

@@ -2450,15 +2450,15 @@ fn gitoxide_unshallows_git_dependencies_that_may_not_be_shallow_anymore() -> any
24502450
.run();
24512451

24522452
assert!(
2453-
!db_clone.is_shallow(),
2454-
"the clone was unshallowed as we need the entire history for revision based checkouts"
2453+
db_clone.is_shallow(),
2454+
"we maintain shallowness and never unshallow"
24552455
);
24562456

24572457
Ok(())
24582458
}
24592459

24602460
#[cargo_test]
2461-
fn gitoxide_uses_shallow_deps_only_when_no_revision_is_specified() -> anyhow::Result<()> {
2461+
fn shallow_deps_work_with_revisions_and_branches_mixed_on_same_dependency() -> anyhow::Result<()> {
24622462
let (bar, bar_repo) = git::new_repo("bar", |p| {
24632463
p.file("Cargo.toml", &basic_manifest("bar", "1.0.0"))
24642464
.file("src/lib.rs", "")
@@ -2507,8 +2507,8 @@ fn gitoxide_uses_shallow_deps_only_when_no_revision_is_specified() -> anyhow::Re
25072507
);
25082508
let db_clone = gix::open_opts(&db_paths[0], gix::open::Options::isolated())?;
25092509
assert!(
2510-
!db_clone.is_shallow(),
2511-
"despite there being two checkouts, it manages to see that the db must not be shallow"
2510+
db_clone.is_shallow(),
2511+
"the repo is shallow while having all data it needs"
25122512
);
25132513

25142514
Ok(())

0 commit comments

Comments
 (0)