Skip to content

Commit 6ede1e2

Browse files
stupendoussuperpowersepage
authored andcommitted
fix(publish): Bail on existing version
1 parent 0a84f1f commit 6ede1e2

File tree

4 files changed

+78
-49
lines changed

4 files changed

+78
-49
lines changed

src/cargo/ops/registry/publish.rs

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ use crate::core::PackageIdSpecQuery;
3131
use crate::core::SourceId;
3232
use crate::core::Workspace;
3333
use crate::ops;
34+
use crate::ops::registry::RegistrySourceIds;
3435
use crate::ops::PackageOpts;
3536
use crate::ops::Packages;
3637
use crate::ops::RegistryOrIndex;
3738
use crate::sources::source::QueryKind;
3839
use crate::sources::source::Source;
40+
use crate::sources::RegistrySource;
3941
use crate::sources::SourceConfigMap;
4042
use crate::sources::CRATES_IO_REGISTRY;
4143
use crate::util::auth;
@@ -45,6 +47,7 @@ use crate::util::toml::prepare_for_publish;
4547
use crate::util::Graph;
4648
use crate::util::Progress;
4749
use crate::util::ProgressStyle;
50+
use crate::util::VersionExt as _;
4851
use crate::CargoResult;
4952
use crate::GlobalContext;
5053

@@ -115,7 +118,7 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
115118
// This is only used to confirm that we can create a token before we build the package.
116119
// This causes the credential provider to be called an extra time, but keeps the same order of errors.
117120
let source_ids = super::get_source_id(opts.gctx, reg_or_index.as_ref())?;
118-
let (mut registry, _) = super::registry(
121+
let (mut registry, mut source) = super::registry(
119122
opts.gctx,
120123
&source_ids,
121124
opts.token.as_ref().map(Secret::as_deref),
@@ -124,9 +127,15 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
124127
Some(Operation::Read).filter(|_| !opts.dry_run),
125128
)?;
126129

127-
// Validate all the packages before publishing any of them.
128-
for (pkg, _) in &pkgs {
129-
verify_dependencies(pkg, &registry, source_ids.original)?;
130+
{
131+
let _lock = opts
132+
.gctx
133+
.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
134+
135+
for (pkg, _) in &pkgs {
136+
verify_unpublished(pkg, &mut source, &source_ids)?;
137+
verify_dependencies(pkg, &registry, source_ids.original)?;
138+
}
130139
}
131140

132141
let pkg_dep_graph = ops::cargo_package::package_with_dep_graph(
@@ -355,6 +364,36 @@ fn poll_one_package(
355364
Ok(!summaries.is_empty())
356365
}
357366

367+
fn verify_unpublished(
368+
pkg: &Package,
369+
source: &mut RegistrySource<'_>,
370+
source_ids: &RegistrySourceIds,
371+
) -> CargoResult<()> {
372+
let query = Dependency::parse(
373+
pkg.name(),
374+
Some(&pkg.version().to_exact_req().to_string()),
375+
source_ids.replacement,
376+
)?;
377+
let duplicate_query = loop {
378+
match source.query_vec(&query, QueryKind::Exact) {
379+
std::task::Poll::Ready(res) => {
380+
break res?;
381+
}
382+
std::task::Poll::Pending => source.block_until_ready()?,
383+
}
384+
};
385+
if !duplicate_query.is_empty() {
386+
bail!(
387+
"crate {}@{} already exists on {}",
388+
pkg.name(),
389+
pkg.version(),
390+
source.describe()
391+
);
392+
}
393+
394+
Ok(())
395+
}
396+
358397
fn verify_dependencies(
359398
pkg: &Package,
360399
registry: &Registry,

tests/testsuite/credential_process.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,31 @@ You may press ctrl-c [..]
544544
.with_stderr_data(output)
545545
.run();
546546

547+
let output_non_independent = r#"[UPDATING] `alternative` index
548+
{"v":1,"registry":{"index-url":"[..]","name":"alternative"},"kind":"get","operation":"read"}
549+
[PACKAGING] foo v0.1.1 ([ROOT]/foo)
550+
[PACKAGED] 3 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
551+
[UPLOADING] foo v0.1.1 ([ROOT]/foo)
552+
{"v":1,"registry":{"index-url":"[..]","name":"alternative"},"kind":"get","operation":"publish","name":"foo","vers":"0.1.1","cksum":"[..]"}
553+
[UPLOADED] foo v0.1.1 to registry `alternative`
554+
[NOTE] waiting [..]
555+
You may press ctrl-c [..]
556+
[PUBLISHED] foo v0.1.1 at registry `alternative`
557+
"#;
558+
559+
p.change_file(
560+
"Cargo.toml",
561+
r#"
562+
[package]
563+
name = "foo"
564+
version = "0.1.1"
565+
edition = "2015"
566+
description = "foo"
567+
license = "MIT"
568+
homepage = "https://example.com/"
569+
"#,
570+
);
571+
547572
p.change_file(
548573
".cargo/config.toml",
549574
&format!(
@@ -557,7 +582,7 @@ You may press ctrl-c [..]
557582
);
558583

559584
p.cargo("publish --registry alternative --no-verify")
560-
.with_stderr_data(output)
585+
.with_stderr_data(output_non_independent)
561586
.run();
562587
}
563588

tests/testsuite/publish.rs

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -132,17 +132,8 @@ You may press ctrl-c to skip waiting; the crate should be available shortly.
132132

133133
#[cargo_test]
134134
fn duplicate_version() {
135-
let registry_dupl = RegistryBuilder::new()
136-
.http_api()
137-
.http_index()
138-
// test registry doesn't error on duplicate versions, we need to
139-
.add_responder("/api/v1/crates/new", move |_req, _server| Response {
140-
code: 200,
141-
headers: vec![],
142-
body: br#"{"errors": [{"detail": "crate version `0.0.1` is already uploaded"}]}"#
143-
.to_vec(),
144-
})
145-
.build();
135+
let registry_dupl = RegistryBuilder::new().http_api().http_index().build();
136+
Package::new("foo", "0.0.1").publish();
146137

147138
let p = project()
148139
.file(
@@ -164,19 +155,7 @@ fn duplicate_version() {
164155
.with_status(101)
165156
.with_stderr_data(str![[r#"
166157
[UPDATING] crates.io index
167-
[WARNING] manifest has no documentation, homepage or repository.
168-
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
169-
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
170-
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
171-
[VERIFYING] foo v0.0.1 ([ROOT]/foo)
172-
[WARNING] no edition set: defaulting to the 2015 edition while the latest is 2021
173-
[COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1)
174-
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
175-
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
176-
[ERROR] failed to publish to registry at http://127.0.0.1:41463/
177-
178-
Caused by:
179-
the remote server responded with an [ERROR] crate version `0.0.1` is already uploaded
158+
[ERROR] crate [email protected] already exists on crates.io index
180159
181160
"#]])
182161
.run();
@@ -3900,22 +3879,7 @@ You may press ctrl-c to skip waiting; the crate should be available shortly.
39003879
.with_status(101)
39013880
.with_stderr_data(str![[r#"
39023881
[UPDATING] crates.io index
3903-
[PACKAGING] a v0.0.1 ([ROOT]/foo/a)
3904-
[PACKAGED] 3 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
3905-
[PACKAGING] b v0.0.1 ([ROOT]/foo/b)
3906-
[PACKAGED] 3 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
3907-
[VERIFYING] a v0.0.1 ([ROOT]/foo/a)
3908-
[COMPILING] a v0.0.1 ([ROOT]/foo/target/package/a-0.0.1)
3909-
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
3910-
[VERIFYING] b v0.0.1 ([ROOT]/foo/b)
3911-
[UPDATING] crates.io index
3912-
[ERROR] failed to verify package tarball
3913-
3914-
Caused by:
3915-
failed to get `a` as a dependency of package `b v0.0.1 ([ROOT]/foo/target/package/b-0.0.1)`
3916-
3917-
Caused by:
3918-
found a package in the remote registry and the local overlay: [email protected]
3882+
[ERROR] crate [email protected] already exists on crates.io index
39193883
39203884
"#]])
39213885
.run();

tests/testsuite/registry_auth.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -565,9 +565,10 @@ fn token_not_logged() {
565565
// 2. config.json again for verification
566566
// 3. /index/3/b/bar
567567
// 4. /dl/bar/1.0.0/download
568-
// 5. /api/v1/crates/new
569-
// 6. config.json for the "wait for publish"
570-
// 7. /index/3/f/foo for the "wait for publish"
571-
assert_eq!(authorizations.len(), 7);
568+
// 5. /index/3/f/foo for checking duplicate version
569+
// 6. /api/v1/crates/new
570+
// 7. config.json for the "wait for publish"
571+
// 8. /index/3/f/foo for the "wait for publish"
572+
assert_eq!(authorizations.len(), 8);
572573
assert!(!log.contains("a-unique_token"));
573574
}

0 commit comments

Comments
 (0)