Auto merge of #8409 - alexcrichton:git-instead-of, r=Eh2406

Improve git error messages a bit

This commit is targeted at further improving the error messages
generated from git errors. For authentication errors the actual URL
fetched is now printed out as well if it's different from the original
URL. This should help handle `insteadOf` logic where SSH urls are used
instead of HTTPS urls and users can know to track that down.

Otherwise the logic about recommending `net.git-fetch-with-cli` was
tweaked a bit and moved to the same location as the rest of our error
reporting.

Note that a change piggy-backed here as well is that `Caused by:` errors
are now automatically all tabbed over a bit instead of only having the
first line tabbed over. This required a good number of tests to be
updated, but it's just an updated in renderings.
This commit is contained in:
bors 2020-06-25 18:47:08 +00:00
commit 8fc3da5e4a
23 changed files with 215 additions and 130 deletions

View File

@ -219,8 +219,8 @@ pub(super) fn activation_error(
};
let mut msg = format!(
"failed to select a version for the requirement `{} = \"{}\"`\n \
candidate versions found which didn't match: {}\n \
"failed to select a version for the requirement `{} = \"{}\"`\n\
candidate versions found which didn't match: {}\n\
location searched: {}\n",
dep.package_name(),
dep.version_req(),

View File

@ -163,7 +163,13 @@ fn _display_error(err: &Error, shell: &mut Shell, as_err: bool) -> bool {
return true;
}
drop(writeln!(shell.err(), "\nCaused by:"));
drop(writeln!(shell.err(), " {}", cause));
for line in cause.to_string().lines() {
if line.is_empty() {
drop(writeln!(shell.err(), ""));
} else {
drop(writeln!(shell.err(), " {}", line));
}
}
}
false
}

View File

@ -3,7 +3,7 @@ use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::paths;
use crate::util::process_builder::process;
use crate::util::{network, Config, IntoUrl, Progress};
use anyhow::anyhow;
use anyhow::{anyhow, Context};
use curl::easy::List;
use git2::{self, ErrorClass, ObjectType};
use log::{debug, info};
@ -85,46 +85,13 @@ impl GitRemote {
locked_rev: Option<git2::Oid>,
cargo_config: &Config,
) -> CargoResult<(GitDatabase, git2::Oid)> {
let format_error = |e: anyhow::Error, operation| {
let may_be_libgit_fault = e
.chain()
.filter_map(|e| e.downcast_ref::<git2::Error>())
.any(|e| match e.class() {
ErrorClass::Net
| ErrorClass::Ssl
| ErrorClass::Submodule
| ErrorClass::FetchHead
| ErrorClass::Ssh
| ErrorClass::Callback
| ErrorClass::Http => true,
_ => false,
});
let uses_cli = cargo_config
.net_config()
.ok()
.and_then(|config| config.git_fetch_with_cli)
.unwrap_or(false);
let msg = if !uses_cli && may_be_libgit_fault {
format!(
r"failed to {} into: {}
If your environment requires git authentication or proxying, try enabling `git-fetch-with-cli`
https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli",
operation,
into.display()
)
} else {
format!("failed to {} into: {}", operation, into.display())
};
e.context(msg)
};
// If we have a previous instance of `GitDatabase` then fetch into that
// if we can. If that can successfully load our revision then we've
// populated the database with the latest version of `reference`, so
// return that database and the rev we resolve to.
if let Some(mut db) = db {
fetch(&mut db.repo, self.url.as_str(), reference, cargo_config)
.map_err(|e| format_error(e, "fetch"))?;
.context(format!("failed to fetch into: {}", into.display()))?;
match locked_rev {
Some(rev) => {
if db.contains(rev) {
@ -148,7 +115,7 @@ impl GitRemote {
paths::create_dir_all(into)?;
let mut repo = init(into, true)?;
fetch(&mut repo, self.url.as_str(), reference, cargo_config)
.map_err(|e| format_error(e, "clone"))?;
.context(format!("failed to clone into: {}", into.display()))?;
let rev = match locked_rev {
Some(rev) => rev,
None => reference.resolve(&repo)?,
@ -481,9 +448,14 @@ where
let mut ssh_agent_attempts = Vec::new();
let mut any_attempts = false;
let mut tried_sshkey = false;
let mut url_attempt = None;
let orig_url = url;
let mut res = f(&mut |url, username, allowed| {
any_attempts = true;
if url != orig_url {
url_attempt = Some(url.to_string());
}
// libgit2's "USERNAME" authentication actually means that it's just
// asking us for a username to keep going. This is currently only really
// used for SSH authentication and isn't really an authentication type.
@ -615,18 +587,26 @@ where
}
}
}
if res.is_ok() || !any_attempts {
return res.map_err(From::from);
}
let mut err = match res {
Ok(e) => return Ok(e),
Err(e) => e,
};
// In the case of an authentication failure (where we tried something) then
// we try to give a more helpful error message about precisely what we
// tried.
let res = res.map_err(anyhow::Error::from).chain_err(|| {
if any_attempts {
let mut msg = "failed to authenticate when downloading \
repository"
.to_string();
if let Some(attempt) = &url_attempt {
if url != attempt {
msg.push_str(": ");
msg.push_str(attempt);
}
}
msg.push_str("\n");
if !ssh_agent_attempts.is_empty() {
let names = ssh_agent_attempts
.iter()
@ -634,28 +614,56 @@ where
.collect::<Vec<_>>()
.join(", ");
msg.push_str(&format!(
"\nattempted ssh-agent authentication, but \
none of the usernames {} succeeded",
"\n* attempted ssh-agent authentication, but \
no usernames succeeded: {}",
names
));
}
if let Some(failed_cred_helper) = cred_helper_bad {
if failed_cred_helper {
msg.push_str(
"\nattempted to find username/password via \
"\n* attempted to find username/password via \
git's `credential.helper` support, but failed",
);
} else {
msg.push_str(
"\nattempted to find username/password via \
"\n* attempted to find username/password via \
`credential.helper`, but maybe the found \
credentials were incorrect",
);
}
}
msg
})?;
Ok(res)
msg.push_str("\n\n");
msg.push_str("if the git CLI succeeds then `net.git-fetch-with-cli` may help here\n");
msg.push_str("https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli");
err = err.context(msg);
// Otherwise if we didn't even get to the authentication phase them we may
// have failed to set up a connection, in these cases hint on the
// `net.git-fetch-with-cli` configuration option.
} else if let Some(e) = err.downcast_ref::<git2::Error>() {
match e.class() {
ErrorClass::Net
| ErrorClass::Ssl
| ErrorClass::Submodule
| ErrorClass::FetchHead
| ErrorClass::Ssh
| ErrorClass::Callback
| ErrorClass::Http => {
let mut msg = "network failure seems to have happened\n".to_string();
msg.push_str(
"if a proxy or similar is necessary `net.git-fetch-with-cli` may help here\n",
);
msg.push_str(
"https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli",
);
err = err.context(msg);
}
_ => {}
}
}
Err(err)
}
fn reset(repo: &git2::Repository, obj: &git2::Object<'_>, config: &Config) -> CargoResult<()> {

View File

@ -20,9 +20,9 @@ impl<'a> Retry<'a> {
match f() {
Err(ref e) if maybe_spurious(e) && self.remaining > 0 => {
let msg = format!(
"spurious network error ({} tries \
remaining): {}",
self.remaining, e
"spurious network error ({} tries remaining): {}",
self.remaining,
e.root_cause(),
);
self.config.shell().warn(msg)?;
self.remaining -= 1;

View File

@ -81,7 +81,7 @@ fn do_read_manifest(
add_unused(manifest.warnings_mut());
if manifest.targets().iter().all(|t| t.is_custom_build()) {
bail!(
"no targets specified in the manifest\n \
"no targets specified in the manifest\n\
either src/lib.rs, src/main.rs, a [lib] section, or \
[[bin]] section must be present"
)

View File

@ -1398,8 +1398,8 @@ Caused by:
Caused by:
invalid configuration for key `target.cfg(not(target_os = \"none\")).runner`
expected a string or array of strings, but found a boolean for \
`target.cfg(not(target_os = \"none\")).runner` in [..]/foo/.cargo/config
expected a string or array of strings, but found a boolean for \
`target.cfg(not(target_os = \"none\")).runner` in [..]/foo/.cargo/config
",
)
.run();

View File

@ -30,7 +30,7 @@ Caused by:
Caused by:
feature `test-dummy-unstable` is required
consider adding `cargo-features = [\"test-dummy-unstable\"]` to the manifest
consider adding `cargo-features = [\"test-dummy-unstable\"]` to the manifest
",
)
.run();
@ -47,9 +47,9 @@ Caused by:
Caused by:
feature `test-dummy-unstable` is required
this Cargo does not support nightly features, but if you
switch to nightly channel you can add
`cargo-features = [\"test-dummy-unstable\"]` to enable this feature
this Cargo does not support nightly features, but if you
switch to nightly channel you can add
`cargo-features = [\"test-dummy-unstable\"]` to enable this feature
",
)
.run();
@ -148,7 +148,7 @@ error: failed to parse manifest at `[..]`
Caused by:
the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
but this is the `stable` channel
See [..]
See [..]
",
)
.run();
@ -213,7 +213,7 @@ Caused by:
Caused by:
the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
but this is the `stable` channel
See [..]
See [..]
",
)
.run();
@ -255,7 +255,7 @@ error: failed to parse manifest at `[..]`
Caused by:
the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
but this is the `stable` channel
See [..]
See [..]
",
)
.run();

View File

@ -192,9 +192,9 @@ error: failed to compile `bar v0.1.0`, intermediate artifacts can be found at `[
Caused by:
no matching package named `baz` found
location searched: registry `https://github.com/rust-lang/crates.io-index`
perhaps you meant: bar or foo
required by package `bar v0.1.0`
location searched: registry `https://github.com/rust-lang/crates.io-index`
perhaps you meant: bar or foo
required by package `bar v0.1.0`
",
)
.run();
@ -663,11 +663,10 @@ Caused by:
Caused by:
the source my-git-repo requires a lock file to be present first before it can be
used against vendored source code
remove the source replacement configuration, generate a lock file, and then
restore the source replacement configuration to continue the build
used against vendored source code
remove the source replacement configuration, generate a lock file, and then
restore the source replacement configuration to continue the build
",
)
.run();
@ -765,8 +764,8 @@ Caused by:
failed to select a version for the requirement `foo = \"^2\"`
candidate versions found which didn't match: 0.0.1
location searched: directory source `[..] (which is replacing registry `[..]`)
required by package `bar v0.1.0`
perhaps a crate was updated and forgotten to be re-vendored?
required by package `bar v0.1.0`
perhaps a crate was updated and forgotten to be re-vendored?
",
)
.with_status(101)

View File

@ -98,7 +98,7 @@ fn invalid3() {
Caused by:
Feature `bar` depends on `baz` which is not an optional dependency.
Consider adding `optional = true` to the dependency
Consider adding `optional = true` to the dependency
",
)
.run();
@ -1456,7 +1456,7 @@ fn namespaced_non_optional_dependency() {
Caused by:
Feature `bar` includes `crate:baz` which is not an optional dependency.
Consider adding `optional = true` to the dependency
Consider adding `optional = true` to the dependency
",
)
.run();
@ -1519,7 +1519,7 @@ fn namespaced_shadowed_dep() {
Caused by:
Feature `baz` includes the optional dependency of the same name, but this is left implicit in the features included by this feature.
Consider adding `crate:baz` to this feature's requirements.
Consider adding `crate:baz` to this feature's requirements.
",
)
.run();
@ -1555,8 +1555,8 @@ fn namespaced_shadowed_non_optional() {
Caused by:
Feature `baz` includes the dependency of the same name, but this is left implicit in the features included by this feature.
Additionally, the dependency must be marked as optional to be included in the feature definition.
Consider adding `crate:baz` to this feature's requirements and marking the dependency as `optional = true`
Additionally, the dependency must be marked as optional to be included in the feature definition.
Consider adding `crate:baz` to this feature's requirements and marking the dependency as `optional = true`
",
)
.run();
@ -1592,7 +1592,7 @@ fn namespaced_implicit_non_optional() {
Caused by:
Feature `bar` includes `baz` which is not defined as a feature.
A non-optional dependency of the same name is defined; consider adding `optional = true` to its definition
A non-optional dependency of the same name is defined; consider adding `optional = true` to its definition
",
).run(
);

View File

@ -1318,7 +1318,7 @@ error: failed to parse manifest at `[..]/foo/Cargo.toml`
Caused by:
feature `resolver` is required
consider adding `cargo-features = [\"resolver\"]` to the manifest
consider adding `cargo-features = [\"resolver\"]` to the manifest
",
)
.run();
@ -1347,7 +1347,7 @@ error: failed to parse manifest at `[..]/foo/Cargo.toml`
Caused by:
feature `resolver` is required
consider adding `cargo-features = [\"resolver\"]` to the manifest
consider adding `cargo-features = [\"resolver\"]` to the manifest
",
)
.run();

View File

@ -3,17 +3,15 @@
use std::collections::HashSet;
use std::io::prelude::*;
use std::io::BufReader;
use std::net::TcpListener;
use std::net::{SocketAddr, TcpListener};
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use std::sync::Arc;
use std::thread;
use std::thread::{self, JoinHandle};
use cargo_test_support::paths;
use cargo_test_support::{basic_manifest, project};
// Tests that HTTP auth is offered from `credential.helper`.
#[cargo_test]
fn http_auth_offered() {
fn setup_failed_auth_test() -> (SocketAddr, JoinHandle<()>, Arc<AtomicUsize>) {
let server = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = server.local_addr().unwrap();
@ -100,7 +98,13 @@ fn http_auth_offered() {
&script.display().to_string().replace("\\", "/"),
)
.unwrap();
(addr, t, connections)
}
// Tests that HTTP auth is offered from `credential.helper`.
#[cargo_test]
fn http_auth_offered() {
let (addr, t, connections) = setup_failed_auth_test();
let p = project()
.file(
"Cargo.toml",
@ -146,7 +150,11 @@ Caused by:
Caused by:
failed to authenticate when downloading repository
attempted to find username/password via `credential.helper`, but [..]
* attempted to find username/password via `credential.helper`, but [..]
if the git CLI succeeds then `net.git-fetch-with-cli` may help here
https://[..]
Caused by:
",
@ -301,8 +309,11 @@ Caused by:
Caused by:
failed to clone into: [..]
If your environment requires git authentication or proxying, try enabling `git-fetch-with-cli`
https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli
Caused by:
network failure seems to have happened
if a proxy or similar is necessary `net.git-fetch-with-cli` may help here
https://[..]
Caused by:
failed to resolve address for needs-proxy.invalid[..]
@ -324,3 +335,64 @@ Caused by:
.with_stderr_does_not_contain("[..]try enabling `git-fetch-with-cli`[..]")
.run();
}
#[cargo_test]
fn instead_of_url_printed() {
let (addr, t, _connections) = setup_failed_auth_test();
let config = paths::home().join(".gitconfig");
let mut config = git2::Config::open(&config).unwrap();
config
.set_str(
&format!("url.http://{}/.insteadOf", addr),
"https://foo.bar/",
)
.unwrap();
let p = project()
.file(
"Cargo.toml",
r#"
[project]
name = "foo"
version = "0.0.1"
authors = []
[dependencies.bar]
git = "https://foo.bar/foo/bar"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("build")
.with_status(101)
.with_stderr(&format!(
"\
[UPDATING] git repository `https://foo.bar/foo/bar`
[ERROR] failed to get `bar` as a dependency of package `foo [..]`
Caused by:
failed to load source for dependency `bar`
Caused by:
Unable to update https://foo.bar/foo/bar
Caused by:
failed to clone into: [..]
Caused by:
failed to authenticate when downloading repository: http://{addr}/foo/bar
* attempted to find username/password via `credential.helper`, but maybe the found credentials were incorrect
if the git CLI succeeds then `net.git-fetch-with-cli` may help here
https://[..]
Caused by:
[..]
",
addr = addr
))
.run();
t.join().ok().unwrap();
}

View File

@ -32,7 +32,7 @@ error: failed to parse manifest at `[..]`
Caused by:
feature `metabuild` is required
consider adding `cargo-features = [\"metabuild\"]` to the manifest
consider adding `cargo-features = [\"metabuild\"]` to the manifest
",
)
.run();

View File

@ -526,8 +526,8 @@ fn offline_resolve_optional_fail() {
.with_status(101)
.with_stderr("\
[ERROR] failed to select a version for the requirement `dep = \"^2.0\"`
candidate versions found which didn't match: 1.0.0
location searched: `[..]` index (which is replacing registry `https://github.com/rust-lang/crates.io-index`)
candidate versions found which didn't match: 1.0.0
location searched: `[..]` index (which is replacing registry `https://github.com/rust-lang/crates.io-index`)
required by package `foo v0.1.0 ([..]/foo)`
perhaps a crate was updated and forgotten to be re-vendored?
As a reminder, you're using offline mode (--offline) which can sometimes cause \

View File

@ -796,7 +796,7 @@ fn do_not_package_if_repository_is_dirty() {
homepage = "foo"
repository = "foo"
# change
"#,
"#,
);
p.cargo("package")
@ -1112,14 +1112,14 @@ error: failed to verify package tarball
Caused by:
Source directory was modified by build.rs during cargo publish. \
Build scripts should not modify anything outside of OUT_DIR.
Changed: [CWD]/target/package/foo-0.0.1/bar.txt
Added: [CWD]/target/package/foo-0.0.1/new-dir
<tab>[CWD]/target/package/foo-0.0.1/src/generated.txt
Removed: [CWD]/target/package/foo-0.0.1/dir
<tab>[CWD]/target/package/foo-0.0.1/dir/foo.txt
Build scripts should not modify anything outside of OUT_DIR.
Changed: [CWD]/target/package/foo-0.0.1/bar.txt
Added: [CWD]/target/package/foo-0.0.1/new-dir
<tab>[CWD]/target/package/foo-0.0.1/src/generated.txt
Removed: [CWD]/target/package/foo-0.0.1/dir
<tab>[CWD]/target/package/foo-0.0.1/dir/foo.txt
To proceed despite this, pass the `--no-verify` flag.",
To proceed despite this, pass the `--no-verify` flag.",
)
.run();

View File

@ -1578,9 +1578,9 @@ Caused by:
Caused by:
patch for `bar` in `registry `[..]/alternative-registry`` resolved to more than one candidate
Found versions: 0.1.0, 0.1.1
Update the patch definition to select only one package.
For example, add an `=` version requirement to the patch definition, such as `version = \"=0.1.1\"`.
Found versions: 0.1.0, 0.1.1
Update the patch definition to select only one package.
For example, add an `=` version requirement to the patch definition, such as `version = \"=0.1.1\"`.
",
)
.run();
@ -1659,9 +1659,9 @@ Caused by:
Caused by:
The patch location `[..]/foo/bar` contains a `bar` package with version `0.1.0`, \
but the patch definition requires `^0.1.1`.
Check that the version in the patch location is what you expect, \
and update the patch definition to match.
but the patch definition requires `^0.1.1`.
Check that the version in the patch location is what you expect, \
and update the patch definition to match.
",
)
.run();
@ -1769,7 +1769,7 @@ Caused by:
Caused by:
The patch location `[..]/foo/bar` contains a `bar` package with version `0.1.0`, but the patch definition requires `^0.1.1`.
Check that the version in the patch location is what you expect, and update the patch definition to match.
Check that the version in the patch location is what you expect, and update the patch definition to match.
",
)
.run();

View File

@ -27,7 +27,7 @@ fn named_profile_gated() {
Caused by:
feature `named-profiles` is required
consider adding `cargo-features = [\"named-profiles\"]` to the manifest
consider adding `cargo-features = [\"named-profiles\"]` to the manifest
",
)
.with_status(101)

View File

@ -537,7 +537,7 @@ fn strip_requires_cargo_feature() {
Caused by:
feature `strip` is required
consider adding `cargo-features = [\"strip\"]` to the manifest
consider adding `cargo-features = [\"strip\"]` to the manifest
",
)
.run();

View File

@ -112,7 +112,7 @@ error: failed to parse manifest at `[..]`
Caused by:
the cargo feature `public-dependency` requires a nightly version of Cargo, but this is the `stable` channel
See https://doc.rust-lang.org/book/appendix-07-nightly-rust.html for more information about Rust release channels.
See https://doc.rust-lang.org/book/appendix-07-nightly-rust.html for more information about Rust release channels.
"
)
.run()
@ -150,7 +150,7 @@ error: failed to parse manifest at `[..]`
Caused by:
feature `public-dependency` is required
consider adding `cargo-features = [\"public-dependency\"]` to the manifest
consider adding `cargo-features = [\"public-dependency\"]` to the manifest
",
)
.run()

View File

@ -224,8 +224,8 @@ fn wrong_version() {
.with_stderr_contains(
"\
error: failed to select a version for the requirement `foo = \">=1.0.0\"`
candidate versions found which didn't match: 0.0.2, 0.0.1
location searched: `[..]` index (which is replacing registry `[..]`)
candidate versions found which didn't match: 0.0.2, 0.0.1
location searched: `[..]` index (which is replacing registry `[..]`)
required by package `foo v0.0.1 ([..])`
",
)
@ -239,8 +239,8 @@ required by package `foo v0.0.1 ([..])`
.with_stderr_contains(
"\
error: failed to select a version for the requirement `foo = \">=1.0.0\"`
candidate versions found which didn't match: 0.0.4, 0.0.3, 0.0.2, ...
location searched: `[..]` index (which is replacing registry `[..]`)
candidate versions found which didn't match: 0.0.4, 0.0.3, 0.0.2, ...
location searched: `[..]` index (which is replacing registry `[..]`)
required by package `foo v0.0.1 ([..])`
",
)
@ -369,8 +369,8 @@ fn package_with_path_deps() {
Caused by:
no matching package named `notyet` found
location searched: registry `https://github.com/rust-lang/crates.io-index`
required by package `foo v0.0.1 [..]`
location searched: registry `https://github.com/rust-lang/crates.io-index`
required by package `foo v0.0.1 [..]`
",
)
.run();
@ -544,8 +544,8 @@ fn relying_on_a_yank_is_bad() {
.with_stderr_contains(
"\
error: failed to select a version for the requirement `baz = \"=0.0.2\"`
candidate versions found which didn't match: 0.0.1
location searched: `[..]` index (which is replacing registry `[..]`)
candidate versions found which didn't match: 0.0.1
location searched: `[..]` index (which is replacing registry `[..]`)
required by package `bar v0.0.1`
... which is depended on by `foo [..]`
",
@ -2118,7 +2118,7 @@ fn registry_index_rejected() {
Caused by:
the `registry.index` config value is no longer supported
Use `[source]` replacement to alter the default index for crates.io.
Use `[source]` replacement to alter the default index for crates.io.
",
)
.run();

View File

@ -548,8 +548,8 @@ fn override_wrong_name() {
Caused by:
no matching package for override `[..]baz:0.1.0` found
location searched: file://[..]
version required: =0.1.0
location searched: file://[..]
version required: =0.1.0
",
)
.run();
@ -682,10 +682,10 @@ fn multiple_specs() {
Caused by:
overlapping replacement specifications found:
* [..]
* [..]
* [..]
* [..]
both specifications match: bar v0.1.0
both specifications match: bar v0.1.0
",
)
.run();

View File

@ -668,7 +668,7 @@ fn install_default_features() {
Caused by:
target `foo` in package `foo` requires the features: `a`
Consider enabling them by passing, e.g., `--features=\"a\"`
Consider enabling them by passing, e.g., `--features=\"a\"`
",
)
.run();
@ -688,7 +688,7 @@ Consider enabling them by passing, e.g., `--features=\"a\"`
Caused by:
target `foo` in package `foo` requires the features: `a`
Consider enabling them by passing, e.g., `--features=\"a\"`
Consider enabling them by passing, e.g., `--features=\"a\"`
",
)
.run();

View File

@ -275,7 +275,7 @@ fn bogus_default_run() {
Caused by:
default-run target `b` not found
<tab>Did you mean `a`?
<tab>Did you mean `a`?
",
)
.run();

View File

@ -504,8 +504,8 @@ error: failed to sync
Caused by:
found duplicate version of package `b v0.5.0` vendored from two sources:
<tab>source 1: [..]
<tab>source 2: [..]
<tab>source 1: [..]
<tab>source 2: [..]
",
)
.with_status(101)