From 19213f3e8835ca9f00364c51dbb52ddd63776233 Mon Sep 17 00:00:00 2001 From: Gabriel Smith Date: Mon, 27 Feb 2017 21:49:54 -0500 Subject: [PATCH 1/8] Teach yank to update max_version where appropriate A crate's max_version is updated when the version we are yanking is the current max_version and when unyanking a version newer than the current max_version. --- src/version.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/version.rs b/src/version.rs index 19cb08ab57d..64b4878c189 100644 --- a/src/version.rs +++ b/src/version.rs @@ -193,6 +193,30 @@ impl Version { pub fn yank(&self, conn: &GenericConnection, yanked: bool) -> CargoResult<()> { conn.execute("UPDATE versions SET yanked = $1 WHERE id = $2", &[&yanked, &self.id])?; + + let rows = conn.query("SELECT max_version FROM crates WHERE id = $1", + &[&self.crate_id])?; + let max_version = rows.iter() + .next() + .map(|r| r.get::<&str, String>("max_version")) + .map(|v| semver::Version::parse(&v).unwrap()) + .unwrap(); + let zero = semver::Version::parse("0.0.0").unwrap(); + let max_update_stmt = conn.prepare("UPDATE crates SET max_version = $1 WHERE id = $2")?; + if yanked && max_version == self.num { + let new_max = conn.query("SELECT num FROM versions \ + WHERE crate_id = $1 AND yanked = FALSE AND id != $2", + &[&self.crate_id, &self.id])? + .iter() + .map(|r| r.get::<&str, String>("num")) + .filter_map(|v| semver::Version::parse(&v).ok()) + .max() + .unwrap(); + max_update_stmt.execute(&[&new_max.to_string(), &self.crate_id])?; + } else if !yanked && (self.num > max_version || max_version == zero) { + max_update_stmt.execute(&[&self.num.to_string(), &self.crate_id])?; + } + Ok(()) } } From 838cb2dd41f5221c769278f54b690aea3de0a39a Mon Sep 17 00:00:00 2001 From: Gabriel Smith Date: Wed, 1 Mar 2017 03:19:22 -0500 Subject: [PATCH 2/8] Allow crate max_version to be null This is required for the situation where the final non-yanked version is yanked leaving no non-yanked version. --- src/krate.rs | 21 +++++++++++++-------- src/tests/all.rs | 2 +- src/tests/krate.rs | 18 ++++++++++++------ src/version.rs | 33 ++++++++++++++++++--------------- 4 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/krate.rs b/src/krate.rs index b2057221c52..c416ef56ca2 100644 --- a/src/krate.rs +++ b/src/krate.rs @@ -44,7 +44,7 @@ pub struct Crate { pub updated_at: Timespec, pub created_at: Timespec, pub downloads: i32, - pub max_version: semver::Version, + pub max_version: Option, pub description: Option, pub homepage: Option, pub documentation: Option, @@ -65,7 +65,7 @@ pub struct EncodableCrate { pub badges: Option>, pub created_at: String, pub downloads: i32, - pub max_version: String, + pub max_version: Option, pub description: Option, pub homepage: Option, pub documentation: Option, @@ -266,7 +266,7 @@ impl Crate { keywords: keyword_ids, categories: category_ids, badges: badges, - max_version: max_version.to_string(), + max_version: max_version.map(|v| v.to_string()), documentation: documentation, homepage: homepage, description: description, @@ -384,12 +384,17 @@ impl Crate { None => {} } let zero = semver::Version::parse("0.0.0").unwrap(); - if *ver > self.max_version || self.max_version == zero { - self.max_version = ver.clone(); + let new_max = match self.max_version { + Some(ref max_version) if *ver > *max_version || *max_version == zero => true, + _ => false, + }; + if new_max { + self.max_version = Some(ver.clone()); } let stmt = conn.prepare("UPDATE crates SET max_version = $1 WHERE id = $2 RETURNING updated_at")?; - let rows = stmt.query(&[&self.max_version.to_string(), &self.id])?; + let max_version = self.max_version.clone().map(|v| v.to_string()); + let rows = stmt.query(&[&max_version, &self.id])?; self.updated_at = rows.get(0).get("updated_at"); Version::insert(conn, self.id, ver, features, authors) } @@ -460,7 +465,7 @@ impl Crate { impl Model for Crate { fn from_row(row: &Row) -> Crate { - let max: String = row.get("max_version"); + let max: Option = row.get("max_version"); Crate { id: row.get("id"), name: row.get("name"), @@ -471,7 +476,7 @@ impl Model for Crate { documentation: row.get("documentation"), homepage: row.get("homepage"), readme: row.get("readme"), - max_version: semver::Version::parse(&max).unwrap(), + max_version: max.map(|m| semver::Version::parse(&m).unwrap()), license: row.get("license"), repository: row.get("repository"), max_upload_size: row.get("max_upload_size"), diff --git a/src/tests/all.rs b/src/tests/all.rs index a008de37d67..cf4b6315a88 100755 --- a/src/tests/all.rs +++ b/src/tests/all.rs @@ -193,7 +193,7 @@ fn krate(name: &str) -> Crate { updated_at: time::now().to_timespec(), created_at: time::now().to_timespec(), downloads: 10, - max_version: semver::Version::parse("0.0.0").unwrap(), + max_version: Some(semver::Version::parse("0.0.0").unwrap()), documentation: None, homepage: None, description: None, diff --git a/src/tests/krate.rs b/src/tests/krate.rs index 8b60d8b77ef..2d277b78b3d 100644 --- a/src/tests/krate.rs +++ b/src/tests/krate.rs @@ -347,7 +347,8 @@ fn new_krate() { let mut response = ok_resp!(middle.call(&mut req)); let json: GoodCrate = ::json(&mut response); assert_eq!(json.krate.name, "foo_new"); - assert_eq!(json.krate.max_version, "1.0.0"); + assert!(json.krate.max_version.is_some()); + assert_eq!(json.krate.max_version.unwrap(), "1.0.0"); } #[test] @@ -360,7 +361,8 @@ fn new_krate_weird_version() { let mut response = ok_resp!(middle.call(&mut req)); let json: GoodCrate = ::json(&mut response); assert_eq!(json.krate.name, "foo_weird"); - assert_eq!(json.krate.max_version, "0.0.0-pre"); + assert!(json.krate.max_version.is_some()); + assert_eq!(json.krate.max_version.unwrap(), "0.0.0-pre"); } #[test] @@ -917,7 +919,8 @@ fn good_categories() { let mut response = ok_resp!(middle.call(&mut req)); let json: GoodCrate = ::json(&mut response); assert_eq!(json.krate.name, "foo_good_cat"); - assert_eq!(json.krate.max_version, "1.0.0"); + assert!(json.krate.max_version.is_some()); + assert_eq!(json.krate.max_version.unwrap(), "1.0.0"); assert_eq!(json.warnings.invalid_categories.len(), 0); } @@ -931,7 +934,8 @@ fn ignored_categories() { let mut response = ok_resp!(middle.call(&mut req)); let json: GoodCrate = ::json(&mut response); assert_eq!(json.krate.name, "foo_ignored_cat"); - assert_eq!(json.krate.max_version, "1.0.0"); + assert!(json.krate.max_version.is_some()); + assert_eq!(json.krate.max_version.unwrap(), "1.0.0"); assert_eq!(json.warnings.invalid_categories, vec!["bar".to_string()]); } @@ -954,7 +958,8 @@ fn good_badges() { let json: GoodCrate = ::json(&mut response); assert_eq!(json.krate.name, "foobadger"); - assert_eq!(json.krate.max_version, "1.0.0"); + assert!(json.krate.max_version.is_some()); + assert_eq!(json.krate.max_version.unwrap(), "1.0.0"); let mut response = ok_resp!( middle.call(req.with_method(Method::Get) @@ -1000,7 +1005,8 @@ fn ignored_badges() { let json: GoodCrate = ::json(&mut response); assert_eq!(json.krate.name, "foo_ignored_badge"); - assert_eq!(json.krate.max_version, "1.0.0"); + assert!(json.krate.max_version.is_some()); + assert_eq!(json.krate.max_version.unwrap(), "1.0.0"); assert_eq!(json.warnings.invalid_badges.len(), 2); assert!(json.warnings.invalid_badges.contains(&"travis-ci".to_string())); assert!(json.warnings.invalid_badges.contains(&"not-a-badge".to_string())); diff --git a/src/version.rs b/src/version.rs index 64b4878c189..0c6656c01c8 100644 --- a/src/version.rs +++ b/src/version.rs @@ -198,22 +198,25 @@ impl Version { &[&self.crate_id])?; let max_version = rows.iter() .next() - .map(|r| r.get::<&str, String>("max_version")) - .map(|v| semver::Version::parse(&v).unwrap()) - .unwrap(); - let zero = semver::Version::parse("0.0.0").unwrap(); + .and_then(|r| r.get::<&str, Option>("max_version")) + .map(|v| semver::Version::parse(&v).unwrap()); let max_update_stmt = conn.prepare("UPDATE crates SET max_version = $1 WHERE id = $2")?; - if yanked && max_version == self.num { - let new_max = conn.query("SELECT num FROM versions \ - WHERE crate_id = $1 AND yanked = FALSE AND id != $2", - &[&self.crate_id, &self.id])? - .iter() - .map(|r| r.get::<&str, String>("num")) - .filter_map(|v| semver::Version::parse(&v).ok()) - .max() - .unwrap(); - max_update_stmt.execute(&[&new_max.to_string(), &self.crate_id])?; - } else if !yanked && (self.num > max_version || max_version == zero) { + if let Some(max_version) = max_version { + let zero = semver::Version::parse("0.0.0").unwrap(); + if yanked && max_version == self.num { + let new_max = conn.query("SELECT num FROM versions \ + WHERE crate_id = $1 AND yanked = FALSE AND id != $2", + &[&self.crate_id, &self.id])? + .iter() + .map(|r| r.get::<&str, String>("num")) + .filter_map(|v| semver::Version::parse(&v).ok()) + .max(); + max_update_stmt.execute(&[&new_max.map(|v| v.to_string()), &self.crate_id])?; + } else if !yanked && (self.num > max_version || max_version == zero) { + max_update_stmt.execute(&[&self.num.to_string(), &self.crate_id])?; + } + } else if !yanked { + // no max version, all versions yanked max_update_stmt.execute(&[&self.num.to_string(), &self.crate_id])?; } From dd40d50c0d8ec49b2fc06cb34aeb1eb2745540a9 Mon Sep 17 00:00:00 2001 From: Gabriel Smith Date: Wed, 1 Mar 2017 05:45:38 -0500 Subject: [PATCH 3/8] Add test for changing max_version on yank/unyank --- src/tests/http-data/krate_yank_max_version | 41 +++++++++++ src/tests/krate.rs | 84 ++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 src/tests/http-data/krate_yank_max_version diff --git a/src/tests/http-data/krate_yank_max_version b/src/tests/http-data/krate_yank_max_version new file mode 100644 index 00000000000..2de5bd31263 --- /dev/null +++ b/src/tests/http-data/krate_yank_max_version @@ -0,0 +1,41 @@ +===REQUEST 339 +PUT http://alexcrichton-test.s3.amazonaws.com/crates/fyk_max/fyk_max-1.0.0.crate HTTP/1.1 +Accept: */* +Proxy-Connection: Keep-Alive +Authorization: AWS AKIAJF3GEK7N44BACDZA:GDxGb6r3SIqo9wXuzHrgMNWekwk= +Content-Length: 0 +Host: alexcrichton-test.s3.amazonaws.com +Content-Type: application/x-tar +Date: Sun, 28 Jun 2015 14:07:17 -0700 + + +===RESPONSE 258 +HTTP/1.1 200 +x-amz-request-id: CB0E925D8E3AB3E8 +x-amz-id-2: SiaMwszM1p2TzXlLauvZ6kRKcUCg7HoyBW29vts42w9ArrLwkJWl8vuvPuGFkpM6XGH+YXN852g= +date: Sun, 28 Jun 2015 21:07:51 GMT +etag: "d41d8cd98f00b204e9800998ecf8427e" +content-length: 0 +server: AmazonS3 + + +===REQUEST 339 +PUT http://alexcrichton-test.s3.amazonaws.com/crates/fyk_max/fyk_max-2.0.0.crate HTTP/1.1 +Accept: */* +Proxy-Connection: Keep-Alive +Authorization: AWS AKIAJF3GEK7N44BACDZA:GDxGb6r3SIqo9wXuzHrgMNWekwk= +Content-Length: 0 +Host: alexcrichton-test.s3.amazonaws.com +Content-Type: application/x-tar +Date: Sun, 28 Jun 2015 14:07:17 -0700 + + +===RESPONSE 258 +HTTP/1.1 200 +x-amz-request-id: CB0E925D8E3AB3E8 +x-amz-id-2: SiaMwszM1p2TzXlLauvZ6kRKcUCg7HoyBW29vts42w9ArrLwkJWl8vuvPuGFkpM6XGH+YXN852g= +date: Sun, 28 Jun 2015 21:07:51 GMT +etag: "d41d8cd98f00b204e9800998ecf8427e" +content-length: 0 +server: AmazonS3 + diff --git a/src/tests/krate.rs b/src/tests/krate.rs index 2d277b78b3d..6bbf80d54ae 100644 --- a/src/tests/krate.rs +++ b/src/tests/krate.rs @@ -871,6 +871,90 @@ fn yank_not_owner() { ::json::<::Bad>(&mut response); } +#[test] +fn yank_max_version() { + #[derive(RustcDecodable)] struct O { ok: bool } + let (_b, app, middle) = ::app(); + + // Upload a new crate + let mut req = ::new_req(app, "fyk_max", "1.0.0"); + ::mock_user(&mut req, ::user("foo")); + let mut response = ok_resp!(middle.call(&mut req)); + + // double check the max version + let json: GoodCrate = ::json(&mut response); + assert!(json.krate.max_version.is_some()); + assert_eq!(json.krate.max_version.unwrap(), "1.0.0"); + + // add version 2.0.0 + let body = ::new_req_body_version_2(::krate("fyk_max")); + let mut response = ok_resp!(middle.call(req.with_path("/api/v1/crates/new") + .with_method(Method::Put) + .with_body(&body))); + let json: GoodCrate = ::json(&mut response); + assert!(json.krate.max_version.is_some()); + assert_eq!(json.krate.max_version.unwrap(), "2.0.0"); + + // yank version 1.0.0 + let mut r = ok_resp!(middle.call(req.with_method(Method::Delete) + .with_path("/api/v1/crates/fyk_max/1.0.0/yank"))); + assert!(::json::(&mut r).ok); + let mut response = ok_resp!(middle.call(req.with_method(Method::Get) + .with_path("/api/v1/crates/fyk_max"))); + let json: CrateResponse = ::json(&mut response); + assert!(json.krate.max_version.is_some()); + assert_eq!(json.krate.max_version.unwrap(), "2.0.0"); + + // unyank version 1.0.0 + let mut r = ok_resp!(middle.call(req.with_method(Method::Put) + .with_path("/api/v1/crates/fyk_max/1.0.0/unyank"))); + assert!(::json::(&mut r).ok); + let mut response = ok_resp!(middle.call(req.with_method(Method::Get) + .with_path("/api/v1/crates/fyk_max"))); + let json: CrateResponse = ::json(&mut response); + assert!(json.krate.max_version.is_some()); + assert_eq!(json.krate.max_version.unwrap(), "2.0.0"); + + // yank version 2.0.0 + let mut r = ok_resp!(middle.call(req.with_method(Method::Delete) + .with_path("/api/v1/crates/fyk_max/2.0.0/yank"))); + assert!(::json::(&mut r).ok); + let mut response = ok_resp!(middle.call(req.with_method(Method::Get) + .with_path("/api/v1/crates/fyk_max"))); + let json: CrateResponse = ::json(&mut response); + assert!(json.krate.max_version.is_some()); + assert_eq!(json.krate.max_version.unwrap(), "1.0.0"); + + // yank version 1.0.0 + let mut r = ok_resp!(middle.call(req.with_method(Method::Delete) + .with_path("/api/v1/crates/fyk_max/1.0.0/yank"))); + assert!(::json::(&mut r).ok); + let mut response = ok_resp!(middle.call(req.with_method(Method::Get) + .with_path("/api/v1/crates/fyk_max"))); + let json: CrateResponse = ::json(&mut response); + assert!(json.krate.max_version.is_none()); + + // unyank version 2.0.0 + let mut r = ok_resp!(middle.call(req.with_method(Method::Put) + .with_path("/api/v1/crates/fyk_max/2.0.0/unyank"))); + assert!(::json::(&mut r).ok); + let mut response = ok_resp!(middle.call(req.with_method(Method::Get) + .with_path("/api/v1/crates/fyk_max"))); + let json: CrateResponse = ::json(&mut response); + assert!(json.krate.max_version.is_some()); + assert_eq!(json.krate.max_version.unwrap(), "2.0.0"); + + // unyank version 1.0.0 + let mut r = ok_resp!(middle.call(req.with_method(Method::Put) + .with_path("/api/v1/crates/fyk_max/1.0.0/unyank"))); + assert!(::json::(&mut r).ok); + let mut response = ok_resp!(middle.call(req.with_method(Method::Get) + .with_path("/api/v1/crates/fyk_max"))); + let json: CrateResponse = ::json(&mut response); + assert!(json.krate.max_version.is_some()); + assert_eq!(json.krate.max_version.unwrap(), "2.0.0"); +} + #[test] fn bad_keywords() { let (_b, app, middle) = ::app(); From 6800937b0a9d2b829620de405a8f128f5330a507 Mon Sep 17 00:00:00 2001 From: Gabriel Smith Date: Wed, 1 Mar 2017 05:58:45 -0500 Subject: [PATCH 4/8] rustfmt run over new yank code --- src/tests/krate.rs | 33 ++++++++++++++++++--------------- src/version.rs | 23 ++++++++++++----------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/tests/krate.rs b/src/tests/krate.rs index 6bbf80d54ae..72481dd1643 100644 --- a/src/tests/krate.rs +++ b/src/tests/krate.rs @@ -873,7 +873,10 @@ fn yank_not_owner() { #[test] fn yank_max_version() { - #[derive(RustcDecodable)] struct O { ok: bool } + #[derive(RustcDecodable)] + struct O { + ok: bool, + } let (_b, app, middle) = ::app(); // Upload a new crate @@ -889,67 +892,67 @@ fn yank_max_version() { // add version 2.0.0 let body = ::new_req_body_version_2(::krate("fyk_max")); let mut response = ok_resp!(middle.call(req.with_path("/api/v1/crates/new") - .with_method(Method::Put) - .with_body(&body))); + .with_method(Method::Put) + .with_body(&body))); let json: GoodCrate = ::json(&mut response); assert!(json.krate.max_version.is_some()); assert_eq!(json.krate.max_version.unwrap(), "2.0.0"); // yank version 1.0.0 let mut r = ok_resp!(middle.call(req.with_method(Method::Delete) - .with_path("/api/v1/crates/fyk_max/1.0.0/yank"))); + .with_path("/api/v1/crates/fyk_max/1.0.0/yank"))); assert!(::json::(&mut r).ok); let mut response = ok_resp!(middle.call(req.with_method(Method::Get) - .with_path("/api/v1/crates/fyk_max"))); + .with_path("/api/v1/crates/fyk_max"))); let json: CrateResponse = ::json(&mut response); assert!(json.krate.max_version.is_some()); assert_eq!(json.krate.max_version.unwrap(), "2.0.0"); // unyank version 1.0.0 let mut r = ok_resp!(middle.call(req.with_method(Method::Put) - .with_path("/api/v1/crates/fyk_max/1.0.0/unyank"))); + .with_path("/api/v1/crates/fyk_max/1.0.0/unyank"))); assert!(::json::(&mut r).ok); let mut response = ok_resp!(middle.call(req.with_method(Method::Get) - .with_path("/api/v1/crates/fyk_max"))); + .with_path("/api/v1/crates/fyk_max"))); let json: CrateResponse = ::json(&mut response); assert!(json.krate.max_version.is_some()); assert_eq!(json.krate.max_version.unwrap(), "2.0.0"); // yank version 2.0.0 let mut r = ok_resp!(middle.call(req.with_method(Method::Delete) - .with_path("/api/v1/crates/fyk_max/2.0.0/yank"))); + .with_path("/api/v1/crates/fyk_max/2.0.0/yank"))); assert!(::json::(&mut r).ok); let mut response = ok_resp!(middle.call(req.with_method(Method::Get) - .with_path("/api/v1/crates/fyk_max"))); + .with_path("/api/v1/crates/fyk_max"))); let json: CrateResponse = ::json(&mut response); assert!(json.krate.max_version.is_some()); assert_eq!(json.krate.max_version.unwrap(), "1.0.0"); // yank version 1.0.0 let mut r = ok_resp!(middle.call(req.with_method(Method::Delete) - .with_path("/api/v1/crates/fyk_max/1.0.0/yank"))); + .with_path("/api/v1/crates/fyk_max/1.0.0/yank"))); assert!(::json::(&mut r).ok); let mut response = ok_resp!(middle.call(req.with_method(Method::Get) - .with_path("/api/v1/crates/fyk_max"))); + .with_path("/api/v1/crates/fyk_max"))); let json: CrateResponse = ::json(&mut response); assert!(json.krate.max_version.is_none()); // unyank version 2.0.0 let mut r = ok_resp!(middle.call(req.with_method(Method::Put) - .with_path("/api/v1/crates/fyk_max/2.0.0/unyank"))); + .with_path("/api/v1/crates/fyk_max/2.0.0/unyank"))); assert!(::json::(&mut r).ok); let mut response = ok_resp!(middle.call(req.with_method(Method::Get) - .with_path("/api/v1/crates/fyk_max"))); + .with_path("/api/v1/crates/fyk_max"))); let json: CrateResponse = ::json(&mut response); assert!(json.krate.max_version.is_some()); assert_eq!(json.krate.max_version.unwrap(), "2.0.0"); // unyank version 1.0.0 let mut r = ok_resp!(middle.call(req.with_method(Method::Put) - .with_path("/api/v1/crates/fyk_max/1.0.0/unyank"))); + .with_path("/api/v1/crates/fyk_max/1.0.0/unyank"))); assert!(::json::(&mut r).ok); let mut response = ok_resp!(middle.call(req.with_method(Method::Get) - .with_path("/api/v1/crates/fyk_max"))); + .with_path("/api/v1/crates/fyk_max"))); let json: CrateResponse = ::json(&mut response); assert!(json.krate.max_version.is_some()); assert_eq!(json.krate.max_version.unwrap(), "2.0.0"); diff --git a/src/version.rs b/src/version.rs index 0c6656c01c8..2dc62b32448 100644 --- a/src/version.rs +++ b/src/version.rs @@ -195,22 +195,23 @@ impl Version { &[&yanked, &self.id])?; let rows = conn.query("SELECT max_version FROM crates WHERE id = $1", - &[&self.crate_id])?; + &[&self.crate_id])?; let max_version = rows.iter() - .next() - .and_then(|r| r.get::<&str, Option>("max_version")) - .map(|v| semver::Version::parse(&v).unwrap()); + .next() + .and_then(|r| r.get::<&str, Option>("max_version")) + .map(|v| semver::Version::parse(&v).unwrap()); let max_update_stmt = conn.prepare("UPDATE crates SET max_version = $1 WHERE id = $2")?; if let Some(max_version) = max_version { let zero = semver::Version::parse("0.0.0").unwrap(); if yanked && max_version == self.num { - let new_max = conn.query("SELECT num FROM versions \ - WHERE crate_id = $1 AND yanked = FALSE AND id != $2", - &[&self.crate_id, &self.id])? - .iter() - .map(|r| r.get::<&str, String>("num")) - .filter_map(|v| semver::Version::parse(&v).ok()) - .max(); + let new_max = + conn.query("SELECT num FROM versions WHERE crate_id = $1 AND yanked = FALSE \ + AND id != $2", + &[&self.crate_id, &self.id])? + .iter() + .map(|r| r.get::<&str, String>("num")) + .filter_map(|v| semver::Version::parse(&v).ok()) + .max(); max_update_stmt.execute(&[&new_max.map(|v| v.to_string()), &self.crate_id])?; } else if !yanked && (self.num > max_version || max_version == zero) { max_update_stmt.execute(&[&self.num.to_string(), &self.crate_id])?; From 1292f7f1c291f71a13f51face15da052a6c64f21 Mon Sep 17 00:00:00 2001 From: Gabriel Smith Date: Sat, 4 Mar 2017 13:59:14 -0500 Subject: [PATCH 5/8] Update max_version if max_version is null when publishing new version --- src/krate.rs | 1 + .../krate_publish_after_yank_max_version | 41 ++++++++++++++++ src/tests/krate.rs | 47 +++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 src/tests/http-data/krate_publish_after_yank_max_version diff --git a/src/krate.rs b/src/krate.rs index c416ef56ca2..fba1dca81b0 100644 --- a/src/krate.rs +++ b/src/krate.rs @@ -385,6 +385,7 @@ impl Crate { } let zero = semver::Version::parse("0.0.0").unwrap(); let new_max = match self.max_version { + None => true, Some(ref max_version) if *ver > *max_version || *max_version == zero => true, _ => false, }; diff --git a/src/tests/http-data/krate_publish_after_yank_max_version b/src/tests/http-data/krate_publish_after_yank_max_version new file mode 100644 index 00000000000..2de5bd31263 --- /dev/null +++ b/src/tests/http-data/krate_publish_after_yank_max_version @@ -0,0 +1,41 @@ +===REQUEST 339 +PUT http://alexcrichton-test.s3.amazonaws.com/crates/fyk_max/fyk_max-1.0.0.crate HTTP/1.1 +Accept: */* +Proxy-Connection: Keep-Alive +Authorization: AWS AKIAJF3GEK7N44BACDZA:GDxGb6r3SIqo9wXuzHrgMNWekwk= +Content-Length: 0 +Host: alexcrichton-test.s3.amazonaws.com +Content-Type: application/x-tar +Date: Sun, 28 Jun 2015 14:07:17 -0700 + + +===RESPONSE 258 +HTTP/1.1 200 +x-amz-request-id: CB0E925D8E3AB3E8 +x-amz-id-2: SiaMwszM1p2TzXlLauvZ6kRKcUCg7HoyBW29vts42w9ArrLwkJWl8vuvPuGFkpM6XGH+YXN852g= +date: Sun, 28 Jun 2015 21:07:51 GMT +etag: "d41d8cd98f00b204e9800998ecf8427e" +content-length: 0 +server: AmazonS3 + + +===REQUEST 339 +PUT http://alexcrichton-test.s3.amazonaws.com/crates/fyk_max/fyk_max-2.0.0.crate HTTP/1.1 +Accept: */* +Proxy-Connection: Keep-Alive +Authorization: AWS AKIAJF3GEK7N44BACDZA:GDxGb6r3SIqo9wXuzHrgMNWekwk= +Content-Length: 0 +Host: alexcrichton-test.s3.amazonaws.com +Content-Type: application/x-tar +Date: Sun, 28 Jun 2015 14:07:17 -0700 + + +===RESPONSE 258 +HTTP/1.1 200 +x-amz-request-id: CB0E925D8E3AB3E8 +x-amz-id-2: SiaMwszM1p2TzXlLauvZ6kRKcUCg7HoyBW29vts42w9ArrLwkJWl8vuvPuGFkpM6XGH+YXN852g= +date: Sun, 28 Jun 2015 21:07:51 GMT +etag: "d41d8cd98f00b204e9800998ecf8427e" +content-length: 0 +server: AmazonS3 + diff --git a/src/tests/krate.rs b/src/tests/krate.rs index 72481dd1643..d57bafbe68e 100644 --- a/src/tests/krate.rs +++ b/src/tests/krate.rs @@ -958,6 +958,53 @@ fn yank_max_version() { assert_eq!(json.krate.max_version.unwrap(), "2.0.0"); } +#[test] +fn publish_after_yank_max_version() { + #[derive(RustcDecodable)] + struct O { + ok: bool, + } + let (_b, app, middle) = ::app(); + + // Upload a new crate + let mut req = ::new_req(app, "fyk_max", "1.0.0"); + ::mock_user(&mut req, ::user("foo")); + let mut response = ok_resp!(middle.call(&mut req)); + + // double check the max version + let json: GoodCrate = ::json(&mut response); + assert!(json.krate.max_version.is_some()); + assert_eq!(json.krate.max_version.unwrap(), "1.0.0"); + + // yank version 1.0.0 + let mut r = ok_resp!(middle.call(req.with_method(Method::Delete) + .with_path("/api/v1/crates/fyk_max/1.0.0/yank"))); + assert!(::json::(&mut r).ok); + let mut response = ok_resp!(middle.call(req.with_method(Method::Get) + .with_path("/api/v1/crates/fyk_max"))); + let json: CrateResponse = ::json(&mut response); + assert!(json.krate.max_version.is_none()); + + // add version 2.0.0 + let body = ::new_req_body_version_2(::krate("fyk_max")); + let mut response = ok_resp!(middle.call(req.with_path("/api/v1/crates/new") + .with_method(Method::Put) + .with_body(&body))); + let json: GoodCrate = ::json(&mut response); + assert!(json.krate.max_version.is_some()); + assert_eq!(json.krate.max_version.unwrap(), "2.0.0"); + + // unyank version 1.0.0 + let mut r = ok_resp!(middle.call(req.with_method(Method::Put) + .with_path("/api/v1/crates/fyk_max/1.0.0/unyank"))); + assert!(::json::(&mut r).ok); + let mut response = ok_resp!(middle.call(req.with_method(Method::Get) + .with_path("/api/v1/crates/fyk_max"))); + let json: CrateResponse = ::json(&mut response); + assert!(json.krate.max_version.is_some()); + assert_eq!(json.krate.max_version.unwrap(), "2.0.0"); +} + #[test] fn bad_keywords() { let (_b, app, middle) = ::app(); From 575829e12fede16d3e71e9c451b3d5704015daea Mon Sep 17 00:00:00 2001 From: Gabriel Smith Date: Sat, 4 Mar 2017 14:44:40 -0500 Subject: [PATCH 6/8] Disable "Version does not exist" error if the version is null --- app/routes/crate/version.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/crate/version.js b/app/routes/crate/version.js index 94c08cb4046..8785c3aca39 100644 --- a/app/routes/crate/version.js +++ b/app/routes/crate/version.js @@ -29,7 +29,7 @@ export default Ember.Route.extend({ return crate.get('versions') .then(versions => { const version = versions.find(version => version.get('num') === params.version_num); - if (!version) { + if (params.version_num && !version) { this.controllerFor('application').set('nextFlashError', `Version '${params.version_num}' of crate '${crate.get('name')}' does not exist`); } From 7cfe73c60e2cea94460578f1478dac20225a2f6f Mon Sep 17 00:00:00 2001 From: Gabriel Smith Date: Sun, 5 Mar 2017 13:43:17 -0500 Subject: [PATCH 7/8] Add task to update the max version of all crates --- src/bin/update-max-versions.rs | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/bin/update-max-versions.rs diff --git a/src/bin/update-max-versions.rs b/src/bin/update-max-versions.rs new file mode 100644 index 00000000000..f3e9b039d32 --- /dev/null +++ b/src/bin/update-max-versions.rs @@ -0,0 +1,35 @@ +// Update the max_version for all crates. +// +// Usage: +// cargo run --bin update-max-versions + +#![deny(warnings)] + +extern crate cargo_registry; +extern crate postgres; +extern crate semver; + +fn main() { + let conn = cargo_registry::db::connect_now(); + { + let tx = conn.transaction().unwrap(); + update(&tx); + tx.set_commit(); + tx.finish().unwrap(); + } +} + +fn update(tx: &postgres::transaction::Transaction) { + let crate_ids = tx.query("SELECT id FROM crates", &[]).unwrap(); + for crate_id in crate_ids.iter() { + let crate_id: i32 = crate_id.get("id"); + let new_max = tx.query("SELECT num FROM versions WHERE crate_id = $1 AND yanked = FALSE", + &[&crate_id]).unwrap() + .iter() + .map(|r| r.get::<&str, String>("num")) + .filter_map(|v| semver::Version::parse(&v).ok()) + .max(); + tx.execute("UPDATE crates SET max_version = $1 WHERE id = $2", + &[&new_max.map(|v| v.to_string()), &crate_id]).unwrap(); + } +} From f6b87064a47b6bea27ea0b82435814b7ccf3b24c Mon Sep 17 00:00:00 2001 From: Gabriel Smith Date: Mon, 6 Mar 2017 11:16:26 -0500 Subject: [PATCH 8/8] Add tests to update-max-versions one-off task --- src/bin/update-max-versions.rs | 70 ++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/bin/update-max-versions.rs b/src/bin/update-max-versions.rs index f3e9b039d32..2448250d21f 100644 --- a/src/bin/update-max-versions.rs +++ b/src/bin/update-max-versions.rs @@ -33,3 +33,73 @@ fn update(tx: &postgres::transaction::Transaction) { &[&new_max.map(|v| v.to_string()), &crate_id]).unwrap(); } } + +#[cfg(test)] +mod test { + use std::collections::HashMap; + + use postgres; + use semver; + + use cargo_registry::{Version, Crate, User, Model, env}; + + fn conn() -> postgres::Connection { + postgres::Connection::connect(&env("TEST_DATABASE_URL")[..], + postgres::TlsMode::None).unwrap() + } + + fn user(conn: &postgres::transaction::Transaction) -> User{ + User::find_or_insert(conn, 2, "login", None, None, None, + "access_token", "api_token").unwrap() + } + + #[test] + fn max_to_null() { + let conn = conn(); + let tx = conn.transaction().unwrap(); + let user = user(&tx); + let krate = Crate::find_or_insert(&tx, "foo", user.id, &None, &None, + &None, &None, &None, &None, + &None, None).unwrap(); + let v1 = semver::Version::parse("1.0.0").unwrap(); + let version = Version::insert(&tx, krate.id, &v1, &HashMap::new(), &[]).unwrap(); + version.yank(&conn, true).unwrap(); + ::update(&tx); + assert_eq!(Crate::find(&tx, krate.id).unwrap().max_version, None); + } + + #[test] + fn max_to_same() { + let conn = conn(); + let tx = conn.transaction().unwrap(); + let user = user(&tx); + let krate = Crate::find_or_insert(&tx, "foo", user.id, &None, &None, + &None, &None, &None, &None, + &None, None).unwrap(); + let v1 = semver::Version::parse("1.0.0").unwrap(); + Version::insert(&tx, krate.id, &v1, &HashMap::new(), &[]).unwrap(); + ::update(&tx); + assert_eq!(Crate::find(&tx, krate.id).unwrap().max_version, Some(v1)); + } + + #[test] + fn multiple_crates() { + let conn = conn(); + let tx = conn.transaction().unwrap(); + let user = user(&tx); + let krate1 = Crate::find_or_insert(&tx, "foo1", user.id, &None, &None, + &None, &None, &None, &None, + &None, None).unwrap(); + let krate2 = Crate::find_or_insert(&tx, "foo2", user.id, &None, &None, + &None, &None, &None, &None, + &None, None).unwrap(); + let v1 = semver::Version::parse("1.0.0").unwrap(); + let krate1_ver = Version::insert(&tx, krate1.id, &v1, &HashMap::new(), + &[]).unwrap(); + Version::insert(&tx, krate2.id, &v1, &HashMap::new(), &[]).unwrap(); + krate1_ver.yank(&conn, true).unwrap(); + ::update(&tx); + assert_eq!(Crate::find(&tx, krate1.id).unwrap().max_version, None); + assert_eq!(Crate::find(&tx, krate2.id).unwrap().max_version, Some(v1)); + } +}