Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up noop registry updates with GitHub #2974

Merged
merged 1 commit into from
Aug 9, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 69 additions & 2 deletions src/cargo/sources/registry/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ use std::io::SeekFrom;
use std::io::prelude::*;
use std::path::Path;

use curl::easy::Easy;
use curl::easy::{Easy, List};
use git2;
use rustc_serialize::json;
use rustc_serialize::hex::ToHex;
use rustc_serialize::json;
use url::Url;

use core::{PackageId, SourceId};
use ops;
Expand Down Expand Up @@ -70,11 +71,30 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> {

try!(self.config.shell().status("Updating",
format!("registry `{}`", self.source_id.url())));

let repo = try!(git2::Repository::open(path).or_else(|_| {
let _ = lock.remove_siblings();
git2::Repository::init(path)
}));

if self.source_id.url().host_str() == Some("github.com") {
if let Ok(oid) = repo.refname_to_id("refs/heads/master") {
let handle = match self.handle {
Some(ref mut handle) => handle,
None => {
self.handle = Some(try!(ops::http_handle(self.config)));
self.handle.as_mut().unwrap()
}
};
debug!("attempting github fast path for {}",
self.source_id.url());
if github_up_to_date(handle, &self.source_id.url(), &oid) {
return Ok(())
}
debug!("fast path failed, falling back to a git fetch");
}
}

// git fetch origin
let url = self.source_id.url().to_string();
let refspec = "refs/heads/*:refs/remotes/origin/*";
Expand Down Expand Up @@ -151,3 +171,50 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> {
Ok(dst)
}
}

/// Updating the index is done pretty regularly so we want it to be as fast as
/// possible. For registries hosted on github (like the crates.io index) there's
/// a fast path available to use [1] to tell us that there's no updates to be
/// made.
///
/// This function will attempt to hit that fast path and verify that the `oid`
/// is actually the current `master` branch of the repository. If `true` is
/// returned then no update needs to be performed, but if `false` is returned
/// then the standard update logic still needs to happen.
///
/// [1]: https://developer.github.com/v3/repos/commits/#get-the-sha-1-of-a-commit-reference
///
/// Note that this function should never cause an actual failure because it's
/// just a fast path. As a result all errors are ignored in this function and we
/// just return a `bool`. Any real errors will be reported through the normal
/// update path above.
fn github_up_to_date(handle: &mut Easy, url: &Url, oid: &git2::Oid) -> bool {
macro_rules! try {
($e:expr) => (match $e {
Some(e) => e,
None => return false,
})
}

// This expects github urls in the form `github.com/user/repo` and nothing
// else
let mut pieces = try!(url.path_segments());
let username = try!(pieces.next());
let repo = try!(pieces.next());
if pieces.next().is_some() {
return false
}

let url = format!("https://api.github.com/repos/{}/{}/commits/master",
username, repo);
try!(handle.get(true).ok());
try!(handle.url(&url).ok());
try!(handle.useragent("cargo").ok());
let mut headers = List::new();
try!(headers.append("Accept: application/vnd.github.3.sha").ok());
try!(headers.append(&format!("If-None-Match: \"{}\"", oid)).ok());
try!(handle.http_headers(headers).ok());
try!(handle.perform().ok());

try!(handle.response_code().ok()) == 304
}