Skip to content

Commit 1072531

Browse files
931: Touch a crate whenever its versions are changed r=carols10cents Prior to this commit, if uploading a new version of a crate didn't otherwise change the metadata (description, etc), the crate's updated_at record wouldn't be changed. The migration specifically checks if `updated_at` changed on the versions to make sure the touch doesn't happen if `downloads` was bumped. I wanted to avoid duplicating that here, so instead this will only happen if the row is new, or as a result of the trigger bumping updated_at on the version. I have specifically not included a query to correct existing data, as it's an irreversable operation. We should manually run a query like `UPDATE crates SET updated_at = MAX(crates.updated_at, SELECT(MAX(versions.updated_at) FROM versions WHERE versions.crate_id = crates.id))` if we want to correct existing data. Fixes #908. Close #925. 936: Add more documentation r=carols10cents As part of the effort to better document crates.io, I added more documentation comments to the `krate::index` and `krate::new` functions as well as to some associated functions. Also added some comments in some functions and to document. Thanks for providing the background on these functions @carols10cents :). I'd like to doc comment the git::yank function as well before this gets merged. 969: crate.scss: When rendering code in README, don't show scrollbar unless necessary r=carols10cents The current `overflow-x: scroll` shows a scrollbar all the time, even for code blocks that don't need them. Switch to `overflow-x: auto`, to only show the scrollbar when needed. 979: CSS: Restrict readme image width r=carols10cents Fixes #971
5 parents cee4c78 + 250c5f2 + 838d957 + ed23aee + 5f8b53b commit 1072531

File tree

11 files changed

+176
-2
lines changed

11 files changed

+176
-2
lines changed

app/styles/crate.scss

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,12 @@
306306
.crate-readme {
307307
line-height: 1.5;
308308

309+
img {
310+
max-width: 100%;
311+
}
312+
309313
pre {
310-
overflow-x: scroll;
314+
overflow-x: auto;
311315
}
312316
}
313317
.last-update {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
DROP TRIGGER touch_crate ON versions;
2+
DROP FUNCTION touch_crate_on_version_modified();
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
CREATE OR REPLACE FUNCTION touch_crate_on_version_modified() RETURNS trigger AS $$
2+
BEGIN
3+
IF (
4+
TG_OP = 'INSERT' OR
5+
NEW.updated_at IS DISTINCT FROM OLD.updated_at
6+
) THEN
7+
UPDATE crates SET updated_at = CURRENT_TIMESTAMP WHERE
8+
crates.id = NEW.crate_id;
9+
END IF;
10+
RETURN NEW;
11+
END;
12+
$$ LANGUAGE plpgsql;
13+
14+
CREATE TRIGGER touch_crate BEFORE INSERT OR UPDATE ON versions
15+
FOR EACH ROW EXECUTE PROCEDURE touch_crate_on_version_modified();

src/git.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ pub fn add_crate(app: &App, krate: &Crate) -> CargoResult<()> {
7373
})
7474
}
7575

76+
/// Yanks or unyanks a crate version. This requires finding the index
77+
/// file, deserlialise the crate from JSON, change the yank boolean to
78+
/// `true` or `false`, write all the lines back out, and commit and
79+
/// push the changes.
7680
pub fn yank(app: &App, krate: &str, version: &semver::Version, yanked: bool) -> CargoResult<()> {
7781
let repo = app.git_repo.lock().unwrap();
7882
let repo_path = repo.workdir().unwrap();
@@ -112,6 +116,22 @@ pub fn yank(app: &App, krate: &str, version: &semver::Version, yanked: bool) ->
112116
})
113117
}
114118

119+
/// Commits and pushes to the crates.io index.
120+
///
121+
/// There are currently 2 instances of the crates.io backend running
122+
/// on Heroku, and they race against each other e.g. if 2 pushes occur,
123+
/// then one will succeed while the other will need to be rebased before
124+
/// being pushed.
125+
///
126+
/// A maximum of 20 attempts to commit and push to the index currently
127+
/// accounts for the amount of traffic publishing crates, though this may
128+
/// have to be changed in the future.
129+
///
130+
/// Notes:
131+
/// Currently, this function is called on the HTTP thread and is blocking.
132+
/// Spawning a separate thread for this function means that the request
133+
/// can return without waiting for completion, and other methods of
134+
/// notifying upon completion or error can be used.
115135
fn commit_and_push<F>(repo: &git2::Repository, mut f: F) -> CargoResult<()>
116136
where
117137
F: FnMut() -> CargoResult<(String, PathBuf)>,

src/krate.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,26 @@ impl Crate {
548548
}
549549

550550
/// Handles the `GET /crates` route.
551+
/// Returns a list of crates. Called in a variety of scenarios in the
552+
/// front end, including:
553+
/// - Alphabetical listing of crates
554+
/// - List of crates under a specific owner
555+
/// - Listing a user's followed crates
556+
///
557+
/// Notes:
558+
/// The different use cases this function covers is handled through passing
559+
/// in parameters in the GET request.
560+
///
561+
/// We would like to stop adding functionality in here. It was built like
562+
/// this to keep the number of database queries low, though given Rust's
563+
/// low performance overhead, this is a soft goal to have, and can afford
564+
/// more database transactions if it aids understandability.
565+
///
566+
/// All of the edge cases for this function are not currently covered
567+
/// in testing, and if they fail, it is difficult to determine what
568+
/// caused the break. In the future, we should look at splitting this
569+
/// function out to cover the different use cases, and create unit tests
570+
/// for them.
551571
pub fn index(req: &mut Request) -> CargoResult<Response> {
552572
use diesel::expression::{AsExpression, DayAndMonthIntervalDsl};
553573
use diesel::types::{Bool, BigInt, Nullable};
@@ -676,6 +696,8 @@ pub fn index(req: &mut Request) -> CargoResult<Response> {
676696
));
677697
}
678698

699+
// The database query returns a tuple within a tuple , with the root
700+
// tuple containing 3 items.
679701
let data = query
680702
.paginate(limit, offset)
681703
.load::<((Crate, bool, Option<i64>), i64)>(&*conn)?;
@@ -863,6 +885,12 @@ pub fn show(req: &mut Request) -> CargoResult<Response> {
863885
}
864886

865887
/// Handles the `PUT /crates/new` route.
888+
/// Used by `cargo publish` to publish a new crate or to publish a new version of an
889+
/// existing crate.
890+
///
891+
/// Currently blocks the HTTP thread, perhaps some function calls can spawn new
892+
/// threads and return completion or error through other methods a `cargo publish
893+
/// --status` command, via crates.io's front end, or email.
866894
pub fn new(req: &mut Request) -> CargoResult<Response> {
867895
let app = req.app().clone();
868896
let (new_crate, user) = parse_new_headers(req)?;
@@ -889,6 +917,8 @@ pub fn new(req: &mut Request) -> CargoResult<Response> {
889917
let categories: Vec<_> = categories.iter().map(|k| &**k).collect();
890918

891919
let conn = req.db_conn()?;
920+
// Create a transaction on the database, if there are no errors,
921+
// commit the transactions to record a new or updated crate.
892922
conn.transaction(|| {
893923
// Persist the new crate, if it doesn't already exist
894924
let persist = NewCrate {
@@ -1012,6 +1042,11 @@ pub fn new(req: &mut Request) -> CargoResult<Response> {
10121042
})
10131043
}
10141044

1045+
/// Used by the `krate::new` function.
1046+
///
1047+
/// This function parses the JSON headers to interpret the data and validates
1048+
/// the data during and after the parsing. Returns crate metadata and user
1049+
/// information.
10151050
fn parse_new_headers(req: &mut Request) -> CargoResult<(upload::NewCrate, User)> {
10161051
// Read the json upload request
10171052
let amt = read_le_u32(req.body())? as u64;

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,14 @@ pub fn middleware(app: Arc<App>) -> MiddlewareBuilder {
148148
C(version::downloads),
149149
);
150150
api_router.get("/crates/:crate_id/:version/authors", C(version::authors));
151+
// Used to generate download graphs
151152
api_router.get("/crates/:crate_id/downloads", C(krate::downloads));
152153
api_router.get("/crates/:crate_id/versions", C(krate::versions));
153154
api_router.put("/crates/:crate_id/follow", C(krate::follow));
154155
api_router.delete("/crates/:crate_id/follow", C(krate::unfollow));
155156
api_router.get("/crates/:crate_id/following", C(krate::following));
157+
// This endpoint may now be redundant, check frontend to see if it is
158+
// being used
156159
api_router.get("/crates/:crate_id/owners", C(krate::owners));
157160
api_router.get("/crates/:crate_id/owner_team", C(krate::owner_team));
158161
api_router.get("/crates/:crate_id/owner_user", C(krate::owner_user));

src/schema.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,5 @@ table! {
183183
license -> Nullable<Varchar>,
184184
}
185185
}
186+
187+
operator_allowed!(crates::updated_at, Sub, sub);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
===REQUEST 371
2+
PUT http://alexcrichton-test.s3.amazonaws.com/crates/foo_versions_updated_at/foo_versions_updated_at-1.0.0.crate HTTP/1.1
3+
Accept: */*
4+
Proxy-Connection: Keep-Alive
5+
Date: Sun, 28 Jun 2015 14:07:18 -0700
6+
Content-Type: application/x-tar
7+
Content-Length: 0
8+
Host: alexcrichton-test.s3.amazonaws.com
9+
Authorization: AWS AKIAJF3GEK7N44BACDZA:B6cKWtg9t24ej4DljrlsMnkgtdg=
10+
11+
12+
===RESPONSE 258
13+
HTTP/1.1 200
14+
x-amz-request-id: D72B3AB3755D34E3
15+
etag: "d41d8cd98f00b204e9800998ecf8427e"
16+
date: Sun, 28 Jun 2015 21:07:51 GMT
17+
x-amz-id-2: 4MEDTSaUXJ7J70JfbXTcfI2m1qoriY+OEOxVAmj2oARpllZTI+vCT52o9FZnVUzJzkchRL407nI=
18+
content-length: 0
19+
server: AmazonS3
20+
21+
22+
===REQUEST 371
23+
PUT http://alexcrichton-test.s3.amazonaws.com/crates/foo_versions_updated_at/foo_versions_updated_at-2.0.0.crate HTTP/1.1
24+
Accept: */*
25+
Proxy-Connection: Keep-Alive
26+
Date: Sun, 28 Jun 2015 14:07:18 -0700
27+
Content-Type: application/x-tar
28+
Content-Length: 0
29+
Host: alexcrichton-test.s3.amazonaws.com
30+
Authorization: AWS AKIAJF3GEK7N44BACDZA:B6cKWtg9t24ej4DljrlsMnkgtdg=
31+
32+
33+
===RESPONSE 258
34+
HTTP/1.1 200
35+
x-amz-request-id: D72B3AB3755D34E3
36+
etag: "d41d8cd98f00b204e9800998ecf8427e"
37+
date: Sun, 28 Jun 2015 21:07:51 GMT
38+
x-amz-id-2: 4MEDTSaUXJ7J70JfbXTcfI2m1qoriY+OEOxVAmj2oARpllZTI+vCT52o9FZnVUzJzkchRL407nI=
39+
content-length: 0
40+
server: AmazonS3
41+
42+

src/tests/krate.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use cargo_registry::krate::{Crate, EncodableCrate, MAX_NAME_LENGTH};
1919

2020
use cargo_registry::token::ApiToken;
2121
use cargo_registry::owner::EncodableOwner;
22-
use cargo_registry::schema::versions;
22+
use cargo_registry::schema::{versions, crates};
2323

2424
use cargo_registry::upload as u;
2525
use cargo_registry::user::EncodablePublicUser;
@@ -463,6 +463,44 @@ fn versions() {
463463
assert_eq!(json.versions[2].num, "0.5.0");
464464
}
465465

466+
#[test]
467+
fn uploading_new_version_touches_crate() {
468+
use diesel::expression::dsl::*;
469+
470+
let (_b, app, middle) = ::app();
471+
472+
let mut upload_req = ::new_req(app.clone(), "foo_versions_updated_at", "1.0.0");
473+
let u = ::sign_in(&mut upload_req, &app);
474+
ok_resp!(middle.call(&mut upload_req));
475+
476+
{
477+
let conn = app.diesel_database.get().unwrap();
478+
diesel::update(crates::table)
479+
.set(crates::updated_at.eq(crates::updated_at - 1.hour()))
480+
.execute(&*conn)
481+
.unwrap();
482+
}
483+
484+
let mut show_req = ::req(
485+
app.clone(),
486+
Method::Get,
487+
"/api/v1/crates/foo_versions_updated_at",
488+
);
489+
let mut response = ok_resp!(middle.call(&mut show_req));
490+
let json: CrateResponse = ::json(&mut response);
491+
let updated_at_before = json.krate.updated_at;
492+
493+
let mut upload_req = ::new_req(app.clone(), "foo_versions_updated_at", "2.0.0");
494+
::sign_in_as(&mut upload_req, &u);
495+
ok_resp!(middle.call(&mut upload_req));
496+
497+
let mut response = ok_resp!(middle.call(&mut show_req));
498+
let json: CrateResponse = ::json(&mut response);
499+
let updated_at_after = json.krate.updated_at;
500+
501+
assert_ne!(updated_at_before, updated_at_after);
502+
}
503+
466504
#[test]
467505
fn new_wrong_token() {
468506
let (_b, app, middle) = ::app();

src/upload.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
//! This module handles the expected information a crate should have
2+
//! and manages the serialising and deserialising of this information
3+
//! to and from structs. The serlializing is only utilised in
4+
//! integration tests.
15
use std::collections::HashMap;
26
use std::ops::Deref;
37

src/version.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,14 @@ pub fn authors(req: &mut Request) -> CargoResult<Response> {
388388
}
389389

390390
/// Handles the `DELETE /crates/:crate_id/:version/yank` route.
391+
/// This does not delete a crate version, it makes the crate
392+
/// version accessible only to crates that already have a
393+
/// `Cargo.lock` containing this version.
394+
///
395+
/// Notes:
396+
/// Crate deletion is not implemented to avoid breaking builds,
397+
/// and the goal of yanking a crate is to prevent crates
398+
/// beginning to depend on the yanked crate version.
391399
pub fn yank(req: &mut Request) -> CargoResult<Response> {
392400
modify_yank(req, true)
393401
}
@@ -397,6 +405,7 @@ pub fn unyank(req: &mut Request) -> CargoResult<Response> {
397405
modify_yank(req, false)
398406
}
399407

408+
/// Changes `yanked` flag on a crate version record
400409
fn modify_yank(req: &mut Request, yanked: bool) -> CargoResult<Response> {
401410
let (version, krate) = version_and_crate(req)?;
402411
let user = req.user()?;

0 commit comments

Comments
 (0)