Skip to content

Commit 4429f4e

Browse files
committed
Auto merge of #5110 - alexcrichton:reset-harder, r=matklad
Try a lot harder to recover corrupt git repos We've received a lot of intermittent bug reports historically about corrupt git repositories. These inevitably happens as Cargo is ctrl-c'd or for whatever other reason, and to provide a better user experience Cargo strives to automatically handle these situations by blowing away the old checkout for a new update. This commit adds a new test which attempts to pathologically corrupt a git database and checkout in an attempt to expose bugs in Cargo. Sure enough there were some more locations that we needed to handle gracefully for corrupt git checkouts. Notable inclusions were: * The `fetch` operation in libgit2 would fail due to corrupt references. This starts by adding an explicit whitelist for classes of errors coming out of `fetch` to auto-retry by blowing away the repository. We need to be super careful here as network errors commonly come out of this function and we don't want to too aggressively re-clone. * After a `fetch` succeeded a repository could fail to actual resolve a git reference to the actual revision we want. This indicated that we indeed needed to blow everything away and re-clone entirely again. * When creating a checkout from a database the `reset` operation might fail due to a corrupt local database of the checkout itself. If this happens we needed to just blow it away and try again. There's likely more lurking situations where we need to re-clone but I figure we can discover those over time.
2 parents 5927a1d + c933673 commit 4429f4e

File tree

13 files changed

+380
-92
lines changed

13 files changed

+380
-92
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ matrix:
3434
script:
3535
- cargo test
3636
- cargo doc --no-deps
37-
- (cd src/doc && mdbook build --no-create --dest-dir ../../target/doc)
37+
- (cd src/doc && mdbook build --dest-dir ../../target/doc)
3838

3939
exclude:
4040
- rust: stable

src/cargo/ops/cargo_clean.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::path::Path;
55
use core::{Profiles, Workspace};
66
use util::Config;
77
use util::errors::{CargoResult, CargoResultExt};
8+
use util::paths;
89
use ops::{self, Context, BuildConfig, Kind, Unit};
910

1011
pub struct CleanOptions<'a> {
@@ -99,14 +100,14 @@ fn rm_rf(path: &Path, config: &Config) -> CargoResult<()> {
99100
let m = fs::metadata(path);
100101
if m.as_ref().map(|s| s.is_dir()).unwrap_or(false) {
101102
config.shell().verbose(|shell| {shell.status("Removing", path.display())})?;
102-
fs::remove_dir_all(path).chain_err(|| {
103+
paths::remove_dir_all(path).chain_err(|| {
103104
format_err!("could not remove build directory")
104105
})?;
105106
} else if m.is_ok() {
106107
config.shell().verbose(|shell| {shell.status("Removing", path.display())})?;
107-
fs::remove_file(path).chain_err(|| {
108+
paths::remove_file(path).chain_err(|| {
108109
format_err!("failed to remove build artifact")
109110
})?;
110111
}
111112
Ok(())
112-
}
113+
}

src/cargo/ops/cargo_install.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use sources::{GitSource, PathSource, SourceConfigMap};
1717
use util::{Config, internal};
1818
use util::{Filesystem, FileLock};
1919
use util::errors::{CargoResult, CargoResultExt};
20+
use util::paths;
2021

2122
#[derive(Deserialize, Serialize)]
2223
#[serde(untagged)]
@@ -47,7 +48,7 @@ impl Transaction {
4748
impl Drop for Transaction {
4849
fn drop(&mut self) {
4950
for bin in self.bins.iter() {
50-
let _ = fs::remove_file(bin);
51+
let _ = paths::remove_file(bin);
5152
}
5253
}
5354
}
@@ -319,7 +320,7 @@ fn install_one(root: &Filesystem,
319320
// Don't bother grabbing a lock as we're going to blow it all away
320321
// anyway.
321322
let target_dir = ws.target_dir().into_path_unlocked();
322-
fs::remove_dir_all(&target_dir)?;
323+
paths::remove_dir_all(&target_dir)?;
323324
}
324325

325326
Ok(())
@@ -667,7 +668,7 @@ pub fn uninstall_one(root: &Filesystem,
667668
write_crate_list(&crate_metadata, metadata)?;
668669
for bin in to_remove {
669670
config.shell().status("Removing", bin.display())?;
670-
fs::remove_file(bin)?;
671+
paths::remove_file(bin)?;
671672
}
672673

673674
Ok(())

src/cargo/ops/cargo_package.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,8 @@ fn run_verify(ws: &Workspace, tar: &FileLock, opts: &PackageOpts) -> CargoResult
304304

305305
let f = GzDecoder::new(tar.file());
306306
let dst = tar.parent().join(&format!("{}-{}", pkg.name(), pkg.version()));
307-
if fs::metadata(&dst).is_ok() {
308-
fs::remove_dir_all(&dst)?;
307+
if dst.exists() {
308+
paths::remove_dir_all(&dst)?;
309309
}
310310
let mut archive = Archive::new(f);
311311
archive.unpack(dst.parent().unwrap())?;

src/cargo/ops/cargo_rustc/mod.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use core::manifest::Lto;
1515
use core::shell::ColorChoice;
1616
use util::{self, ProcessBuilder, machine_message};
1717
use util::{Config, internal, profile, join_paths};
18+
use util::paths;
1819
use util::errors::{CargoResult, CargoResultExt, Internal};
1920
use util::Freshness;
2021

@@ -385,9 +386,7 @@ fn rustc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>,
385386
if filename.extension() == Some(OsStr::new("rmeta")) {
386387
let dst = root.join(filename).with_extension("rlib");
387388
if dst.exists() {
388-
fs::remove_file(&dst).chain_err(|| {
389-
format!("Could not remove file: {}.", dst.display())
390-
})?;
389+
paths::remove_file(&dst)?;
391390
}
392391
}
393392
}
@@ -541,9 +540,7 @@ fn link_targets<'a, 'cfg>(cx: &mut Context<'a, 'cfg>,
541540
continue
542541
}
543542
if dst.exists() {
544-
fs::remove_file(&dst).chain_err(|| {
545-
format!("failed to remove: {}", dst.display())
546-
})?;
543+
paths::remove_file(&dst)?;
547544
}
548545

549546
let link_result = if src.is_dir() {

src/cargo/ops/cargo_rustc/output_depinfo.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use std::collections::HashSet;
2-
use std::io::{Write, BufWriter, ErrorKind};
3-
use std::fs::{self, File};
2+
use std::io::{Write, BufWriter};
3+
use std::fs::File;
44
use std::path::{Path, PathBuf};
55

66
use ops::{Context, Unit};
77
use util::{CargoResult, internal};
8+
use util::paths;
89
use ops::cargo_rustc::fingerprint;
910

1011
fn render_filename<P: AsRef<Path>>(path: P, basedir: Option<&str>) -> CargoResult<String> {
@@ -81,13 +82,12 @@ pub fn output_depinfo<'a, 'b>(context: &mut Context<'a, 'b>, unit: &Unit<'a>) ->
8182
write!(outfile, " {}", render_filename(dep, basedir)?)?;
8283
}
8384
writeln!(outfile, "")?;
84-
} else if let Err(err) = fs::remove_file(output_path) {
85-
// dep-info generation failed, so delete output file. This will usually
86-
// cause the build system to always rerun the build rule, which is correct
87-
// if inefficient.
88-
if err.kind() != ErrorKind::NotFound {
89-
return Err(err.into());
90-
}
85+
86+
// dep-info generation failed, so delete output file. This will
87+
// usually cause the build system to always rerun the build
88+
// rule, which is correct if inefficient.
89+
} else if output_path.exists() {
90+
paths::remove_file(output_path)?;
9191
}
9292
}
9393
}

src/cargo/sources/git/source.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use core::source::{Source, SourceId};
66
use core::GitReference;
77
use core::{Package, PackageId, Summary, Registry, Dependency};
88
use util::Config;
9-
use util::errors::{CargoResult, Internal};
9+
use util::errors::CargoResult;
1010
use util::hex::short_hash;
1111
use sources::PathSource;
1212
use sources::git::utils::{GitRemote, GitRevision};
@@ -170,9 +170,7 @@ impl<'cfg> Source for GitSource<'cfg> {
170170

171171
trace!("updating git source `{:?}`", self.remote);
172172

173-
let repo = self.remote.checkout(&db_path, self.config)?;
174-
let rev = repo.rev_for(&self.reference).map_err(Internal::new)?;
175-
(repo, rev)
173+
self.remote.checkout(&db_path, &self.reference, self.config)?
176174
} else {
177175
(self.remote.db_at(&db_path)?, actual_rev.unwrap())
178176
};

0 commit comments

Comments
 (0)