Skip to content

Commit c321437

Browse files
committed
Auto merge of #9392 - alexcrichton:fix-patch-git-head-issue, r=ehuss
Fix loading `branch=master` patches in the v3 lock transition This commit fixes an issue pointed out during #9352 where in the v2->v3 lock file transition (currently happening on nightly) Cargo will not correctly use the previous lock file entry for `[patch]` directives that point to git dependencies using `branch = 'master'` explicitly. The reason for this is that Cargo previously, with the v2 format, considered `branch=master` and `DefaultBranch` to be equivalent dependencies. Now that Cargo treats those as distinct resolve nodes we need to load lock files that use `DefaultBranch` and transparently use those for `branch=master` dependencies. These lock file nodes do not naturally unify so we have to go out of our way to get the two to line up in modern Cargo. This was previously done for the lock file at large, but the previous logic didn't take `[patch]` into account. Unfortunately almost everything to do with `[patch]` and lock files is pretty complicated, and this is no exception. The fix here is wordy, verbose, and quite subtle in how it works. I'm pretty sure it does work though and I think that this should be good enough to at least transition most users off the v2 lock file format. Once this has baked in Cargo for some time (on the scale of a year) I would hope that we could just remove this logic since it's only really here for a transitionary period. Closes #9352
2 parents 4bcbf87 + fe48497 commit c321437

File tree

3 files changed

+221
-36
lines changed

3 files changed

+221
-36
lines changed

src/cargo/core/registry.rs

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,21 @@ enum Kind {
107107
Normal,
108108
}
109109

110+
/// Argument to `PackageRegistry::patch` which is information about a `[patch]`
111+
/// directive that we found in a lockfile, if present.
112+
pub struct LockedPatchDependency {
113+
/// The original `Dependency` directive, except "locked" so it's version
114+
/// requirement is `=foo` and its `SourceId` has a "precise" listed.
115+
pub dependency: Dependency,
116+
/// The `PackageId` that was previously found in a lock file which
117+
/// `dependency` matches.
118+
pub package_id: PackageId,
119+
/// Something only used for backwards compatibility with the v2 lock file
120+
/// format where `branch=master` is considered the same as `DefaultBranch`.
121+
/// For more comments on this see the code in `ops/resolve.rs`.
122+
pub alt_package_id: Option<PackageId>,
123+
}
124+
110125
impl<'cfg> PackageRegistry<'cfg> {
111126
pub fn new(config: &'cfg Config) -> CargoResult<PackageRegistry<'cfg>> {
112127
let source_config = SourceConfigMap::new(config)?;
@@ -240,7 +255,7 @@ impl<'cfg> PackageRegistry<'cfg> {
240255
pub fn patch(
241256
&mut self,
242257
url: &Url,
243-
deps: &[(&Dependency, Option<(Dependency, PackageId)>)],
258+
deps: &[(&Dependency, Option<LockedPatchDependency>)],
244259
) -> CargoResult<Vec<(Dependency, PackageId)>> {
245260
// NOTE: None of this code is aware of required features. If a patch
246261
// is missing a required feature, you end up with an "unused patch"
@@ -268,7 +283,7 @@ impl<'cfg> PackageRegistry<'cfg> {
268283
let orig_patch = *orig_patch;
269284
// Use the locked patch if it exists, otherwise use the original.
270285
let dep = match locked {
271-
Some((locked_patch, _locked_id)) => locked_patch,
286+
Some(lock) => &lock.dependency,
272287
None => orig_patch,
273288
};
274289
debug!(
@@ -338,13 +353,36 @@ impl<'cfg> PackageRegistry<'cfg> {
338353
}
339354
}
340355

356+
// Calculate a list of all patches available for this source which is
357+
// then used later during calls to `lock` to rewrite summaries to point
358+
// directly at these patched entries.
359+
//
360+
// Note that this is somewhat subtle where the list of `ids` for a
361+
// canonical URL is extend with possibly two ids per summary. This is done
362+
// to handle the transition from the v2->v3 lock file format where in
363+
// v2 DefeaultBranch was either DefaultBranch or Branch("master") for
364+
// git dependencies. In this case if `summary.package_id()` is
365+
// Branch("master") then alt_package_id will be DefaultBranch. This
366+
// signifies that there's a patch available for either of those
367+
// dependency directives if we see them in the dependency graph.
368+
//
369+
// This is a bit complicated and hopefully an edge case we can remove
370+
// in the future, but for now it hopefully doesn't cause too much
371+
// harm...
372+
let mut ids = Vec::new();
373+
for (summary, (_, lock)) in unlocked_summaries.iter().zip(deps) {
374+
ids.push(summary.package_id());
375+
if let Some(lock) = lock {
376+
ids.extend(lock.alt_package_id);
377+
}
378+
}
379+
self.patches_available.insert(canonical.clone(), ids);
380+
341381
// Note that we do not use `lock` here to lock summaries! That step
342382
// happens later once `lock_patches` is invoked. In the meantime though
343383
// we want to fill in the `patches_available` map (later used in the
344384
// `lock` method) and otherwise store the unlocked summaries in
345385
// `patches` to get locked in a future call to `lock_patches`.
346-
let ids = unlocked_summaries.iter().map(|s| s.package_id()).collect();
347-
self.patches_available.insert(canonical.clone(), ids);
348386
self.patches.insert(canonical, unlocked_summaries);
349387

350388
Ok(unlock_patches)
@@ -747,7 +785,7 @@ fn lock(
747785
/// This is a helper for selecting the summary, or generating a helpful error message.
748786
fn summary_for_patch(
749787
orig_patch: &Dependency,
750-
locked: &Option<(Dependency, PackageId)>,
788+
locked: &Option<LockedPatchDependency>,
751789
mut summaries: Vec<Summary>,
752790
source: &mut dyn Source,
753791
) -> CargoResult<(Summary, Option<PackageId>)> {
@@ -779,7 +817,7 @@ fn summary_for_patch(
779817
}
780818
assert!(summaries.is_empty());
781819
// No summaries found, try to help the user figure out what is wrong.
782-
if let Some((_locked_patch, locked_id)) = locked {
820+
if let Some(locked) = locked {
783821
// Since the locked patch did not match anything, try the unlocked one.
784822
let orig_matches = source.query_vec(orig_patch).unwrap_or_else(|e| {
785823
log::warn!(
@@ -792,7 +830,7 @@ fn summary_for_patch(
792830
let (summary, _) = summary_for_patch(orig_patch, &None, orig_matches, source)?;
793831
// The unlocked version found a match. This returns a value to
794832
// indicate that this entry should be unlocked.
795-
return Ok((summary, Some(*locked_id)));
833+
return Ok((summary, Some(locked.package_id)));
796834
}
797835
// Try checking if there are *any* packages that match this by name.
798836
let name_only_dep = Dependency::new_override(orig_patch.package_name(), orig_patch.source_id());

src/cargo/ops/resolve.rs

Lines changed: 99 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
//! providing the most power and flexibility.
1212
1313
use crate::core::compiler::{CompileKind, RustcTargetData};
14-
use crate::core::registry::PackageRegistry;
14+
use crate::core::registry::{LockedPatchDependency, PackageRegistry};
1515
use crate::core::resolver::features::{
1616
CliFeatures, FeatureOpts, FeatureResolver, ForceAllTargets, RequestedFeatures, ResolvedFeatures,
1717
};
@@ -257,26 +257,91 @@ pub fn resolve_with_previous<'cfg>(
257257
continue;
258258
}
259259
};
260-
let patches = patches
261-
.iter()
262-
.map(|dep| {
263-
let unused = previous.unused_patches().iter().cloned();
264-
let candidates = previous.iter().chain(unused);
265-
match candidates
266-
.filter(pre_patch_keep)
267-
.find(|&id| dep.matches_id(id))
268-
{
269-
Some(id) => {
270-
let mut locked_dep = dep.clone();
271-
locked_dep.lock_to(id);
272-
(dep, Some((locked_dep, id)))
260+
261+
// This is a list of pairs where the first element of the pair is
262+
// the raw `Dependency` which matches what's listed in `Cargo.toml`.
263+
// The second element is, if present, the "locked" version of
264+
// the `Dependency` as well as the `PackageId` that it previously
265+
// resolved to. This second element is calculated by looking at the
266+
// previous resolve graph, which is primarily what's done here to
267+
// build the `registrations` list.
268+
let mut registrations = Vec::new();
269+
for dep in patches {
270+
let candidates = || {
271+
previous
272+
.iter()
273+
.chain(previous.unused_patches().iter().cloned())
274+
.filter(&pre_patch_keep)
275+
};
276+
277+
let lock = match candidates().find(|id| dep.matches_id(*id)) {
278+
// If we found an exactly matching candidate in our list of
279+
// candidates, then that's the one to use.
280+
Some(package_id) => {
281+
let mut locked_dep = dep.clone();
282+
locked_dep.lock_to(package_id);
283+
Some(LockedPatchDependency {
284+
dependency: locked_dep,
285+
package_id,
286+
alt_package_id: None,
287+
})
288+
}
289+
None => {
290+
// If the candidate does not have a matching source id
291+
// then we may still have a lock candidate. If we're
292+
// loading a v2-encoded resolve graph and `dep` is a
293+
// git dep with `branch = 'master'`, then this should
294+
// also match candidates without `branch = 'master'`
295+
// (which is now treated separately in Cargo).
296+
//
297+
// In this scenario we try to convert candidates located
298+
// in the resolve graph to explicitly having the
299+
// `master` branch (if they otherwise point to
300+
// `DefaultBranch`). If this works and our `dep`
301+
// matches that then this is something we'll lock to.
302+
match candidates().find(|&id| {
303+
match master_branch_git_source(id, previous) {
304+
Some(id) => dep.matches_id(id),
305+
None => false,
306+
}
307+
}) {
308+
Some(id_using_default) => {
309+
let id_using_master = id_using_default.with_source_id(
310+
dep.source_id().with_precise(
311+
id_using_default
312+
.source_id()
313+
.precise()
314+
.map(|s| s.to_string()),
315+
),
316+
);
317+
318+
let mut locked_dep = dep.clone();
319+
locked_dep.lock_to(id_using_master);
320+
Some(LockedPatchDependency {
321+
dependency: locked_dep,
322+
package_id: id_using_master,
323+
// Note that this is where the magic
324+
// happens, where the resolve graph
325+
// probably has locks pointing to
326+
// DefaultBranch sources, and by including
327+
// this here those will get transparently
328+
// rewritten to Branch("master") which we
329+
// have a lock entry for.
330+
alt_package_id: Some(id_using_default),
331+
})
332+
}
333+
334+
// No locked candidate was found
335+
None => None,
273336
}
274-
None => (dep, None),
275337
}
276-
})
277-
.collect::<Vec<_>>();
338+
};
339+
340+
registrations.push((dep, lock));
341+
}
342+
278343
let canonical = CanonicalUrl::new(url)?;
279-
for (orig_patch, unlock_id) in registry.patch(url, &patches)? {
344+
for (orig_patch, unlock_id) in registry.patch(url, &registrations)? {
280345
// Avoid the locked patch ID.
281346
avoid_patch_ids.insert(unlock_id);
282347
// Also avoid the thing it is patching.
@@ -624,17 +689,8 @@ fn register_previous_locks(
624689
// Note that this is only applicable for loading older resolves now at
625690
// this point. All new lock files are encoded as v3-or-later, so this is
626691
// just compat for loading an old lock file successfully.
627-
if resolve.version() <= ResolveVersion::V2 {
628-
let source = node.source_id();
629-
if let Some(GitReference::DefaultBranch) = source.git_reference() {
630-
let new_source =
631-
SourceId::for_git(source.url(), GitReference::Branch("master".to_string()))
632-
.unwrap()
633-
.with_precise(source.precise().map(|s| s.to_string()));
634-
635-
let node = node.with_source_id(new_source);
636-
registry.register_lock(node, deps.clone());
637-
}
692+
if let Some(node) = master_branch_git_source(node, resolve) {
693+
registry.register_lock(node, deps.clone());
638694
}
639695

640696
registry.register_lock(node, deps);
@@ -651,3 +707,17 @@ fn register_previous_locks(
651707
}
652708
}
653709
}
710+
711+
fn master_branch_git_source(id: PackageId, resolve: &Resolve) -> Option<PackageId> {
712+
if resolve.version() <= ResolveVersion::V2 {
713+
let source = id.source_id();
714+
if let Some(GitReference::DefaultBranch) = source.git_reference() {
715+
let new_source =
716+
SourceId::for_git(source.url(), GitReference::Branch("master".to_string()))
717+
.unwrap()
718+
.with_precise(source.precise().map(|s| s.to_string()));
719+
return Some(id.with_source_id(new_source));
720+
}
721+
}
722+
None
723+
}

tests/testsuite/patch.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2320,3 +2320,80 @@ fn can_update_with_alt_reg() {
23202320
)
23212321
.run();
23222322
}
2323+
2324+
#[cargo_test]
2325+
fn old_git_patch() {
2326+
// Example where an old lockfile with an explicit branch="master" in Cargo.toml.
2327+
Package::new("bar", "1.0.0").publish();
2328+
let (bar, bar_repo) = git::new_repo("bar", |p| {
2329+
p.file("Cargo.toml", &basic_manifest("bar", "1.0.0"))
2330+
.file("src/lib.rs", "")
2331+
});
2332+
2333+
let bar_oid = bar_repo.head().unwrap().target().unwrap();
2334+
2335+
let p = project()
2336+
.file(
2337+
"Cargo.toml",
2338+
&format!(
2339+
r#"
2340+
[package]
2341+
name = "foo"
2342+
version = "0.1.0"
2343+
2344+
[dependencies]
2345+
bar = "1.0"
2346+
2347+
[patch.crates-io]
2348+
bar = {{ git = "{}", branch = "master" }}
2349+
"#,
2350+
bar.url()
2351+
),
2352+
)
2353+
.file(
2354+
"Cargo.lock",
2355+
&format!(
2356+
r#"
2357+
# This file is automatically @generated by Cargo.
2358+
# It is not intended for manual editing.
2359+
[[package]]
2360+
name = "bar"
2361+
version = "1.0.0"
2362+
source = "git+{}#{}"
2363+
2364+
[[package]]
2365+
name = "foo"
2366+
version = "0.1.0"
2367+
dependencies = [
2368+
"bar",
2369+
]
2370+
"#,
2371+
bar.url(),
2372+
bar_oid
2373+
),
2374+
)
2375+
.file("src/lib.rs", "")
2376+
.build();
2377+
2378+
bar.change_file("Cargo.toml", &basic_manifest("bar", "2.0.0"));
2379+
git::add(&bar_repo);
2380+
git::commit(&bar_repo);
2381+
2382+
// This *should* keep the old lock.
2383+
p.cargo("tree")
2384+
// .env("CARGO_LOG", "trace")
2385+
.with_stderr(
2386+
"\
2387+
[UPDATING] [..]
2388+
",
2389+
)
2390+
// .with_status(1)
2391+
.with_stdout(format!(
2392+
"\
2393+
foo v0.1.0 [..]
2394+
└── bar v1.0.0 (file:///[..]branch=master#{})
2395+
",
2396+
&bar_oid.to_string()[..8]
2397+
))
2398+
.run();
2399+
}

0 commit comments

Comments
 (0)