From 008068184773f9fdd53a6d5bfee4e038a857e135 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 13 Jan 2015 21:40:22 -0800 Subject: [PATCH] Scan untracked files in git for packaging Closes #797 --- src/cargo/sources/path.rs | 88 +++++++++++++++++++++++-------------- tests/test_cargo_package.rs | 25 ++++++++++- 2 files changed, 77 insertions(+), 36 deletions(-) diff --git a/src/cargo/sources/path.rs b/src/cargo/sources/path.rs index c1278c18e1f..0f5c2cbaf73 100644 --- a/src/cargo/sources/path.rs +++ b/src/cargo/sources/path.rs @@ -113,23 +113,36 @@ impl PathSource { { warn!("list_files_git {}", pkg.get_package_id()); let index = try!(repo.index()); - let root = match repo.workdir() { - Some(dir) => dir, - None => return Err(internal_error("Can't list files on a bare repository.", "")), - }; + let root = try!(repo.workdir().chain_error(|| { + internal_error("Can't list files on a bare repository.", "") + })); let pkg_path = pkg.get_manifest_path().dir_path(); let mut ret = Vec::new(); - 'outer: for entry in index.iter() { - let fname = entry.path.as_slice(); - let file_path = root.join(fname); + // We use information from the git repository to guide use in traversing + // its tree. The primary purpose of this is to take advantage of the + // .gitignore and auto-ignore files that don't matter. + // + // If the repository has no commits, then we check the status of all + // files (tracked and untracked) and use all those. If the repository + // has at least one commit, however, we assume that all relevant files + // are in the index so we filter out all the statuses. + let index_files = index.iter().map(|entry| root.join(&entry.path[])); + let mut opts = git2::StatusOptions::new(); + let unborn = repo.head().is_err(); + opts.include_untracked(true); + let statuses = try!(repo.statuses(Some(&mut opts))); + let untracked = statuses.iter().map(|entry| root.join(entry.path_bytes())) + .filter(|_| unborn); + + 'outer: for file_path in index_files.chain(untracked) { // Filter out files outside this package. if !pkg_path.is_ancestor_of(&file_path) { continue } // Filter out Cargo.lock and target always - if fname == b"Cargo.lock" { continue } - if fname == b"target" { continue } + if file_path.filename() == Some(b"Cargo.lock") { continue } + if file_path.filename() == Some(b"target") { continue } // Filter out sub-packages of this package for other_pkg in self.packages.iter().filter(|p| *p != pkg) { @@ -148,10 +161,16 @@ impl PathSource { let rel = try!(rel.as_str().chain_error(|| { human(format!("invalid utf-8 filename: {}", rel.display())) })); - let submodule = try!(repo.find_submodule(rel)); - let repo = try!(submodule.open()); - let files = try!(self.list_files_git(pkg, repo, filter)); - ret.extend(files.into_iter()); + match repo.find_submodule(rel) { + Ok(submodule) => { + let repo = try!(submodule.open()); + let files = try!(self.list_files_git(pkg, repo, filter)); + ret.extend(files.into_iter()); + } + Err(..) => { + try!(self.walk(&file_path, &mut ret, false, filter)); + } + } } else if (*filter)(&file_path) { // We found a file! warn!(" found {}", file_path.display()); @@ -168,33 +187,34 @@ impl PathSource { let mut ret = Vec::new(); for pkg in self.packages.iter().filter(|p| *p == pkg) { let loc = pkg.get_manifest_path().dir_path(); - try!(walk(&loc, &mut ret, true, &mut filter)); + try!(self.walk(&loc, &mut ret, true, &mut filter)); } return Ok(ret); - fn walk(path: &Path, ret: &mut Vec, - is_root: bool, filter: &mut F) -> CargoResult<()> - where F: FnMut(&Path) -> bool - { - if !path.is_dir() { - if (*filter)(path) { - ret.push(path.clone()); - } - return Ok(()) - } - // Don't recurse into any sub-packages that we have - if !is_root && path.join("Cargo.toml").exists() { return Ok(()) } - for dir in try!(fs::readdir(path)).iter() { - match (is_root, dir.filename_str()) { - (_, Some(".git")) | - (true, Some("target")) | - (true, Some("Cargo.lock")) => continue, - _ => {} - } - try!(walk(dir, ret, false, filter)); + } + + fn walk(&self, path: &Path, ret: &mut Vec, + is_root: bool, filter: &mut F) -> CargoResult<()> + where F: FnMut(&Path) -> bool + { + if !path.is_dir() { + if (*filter)(path) { + ret.push(path.clone()); } return Ok(()) } + // Don't recurse into any sub-packages that we have + if !is_root && path.join("Cargo.toml").exists() { return Ok(()) } + for dir in try!(fs::readdir(path)).iter() { + match (is_root, dir.filename_str()) { + (_, Some(".git")) | + (true, Some("target")) | + (true, Some("Cargo.lock")) => continue, + _ => {} + } + try!(self.walk(dir, ret, false, filter)); + } + return Ok(()) } } diff --git a/tests/test_cargo_package.rs b/tests/test_cargo_package.rs index ccbc55a5a6d..30937c33899 100644 --- a/tests/test_cargo_package.rs +++ b/tests/test_cargo_package.rs @@ -1,8 +1,9 @@ use std::io::{File, MemReader}; -use tar::Archive; -use flate2::reader::GzDecoder; use cargo::util::process; +use flate2::reader::GzDecoder; +use git2; +use tar::Archive; use support::{project, execs, cargo_dir, paths, git}; use support::{PACKAGING, VERIFYING, COMPILING, ARCHIVING}; @@ -242,3 +243,23 @@ test!(include { {archiving} [..] ", packaging = PACKAGING, archiving = ARCHIVING).as_slice())); }); + +test!(package_new_git_repo { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.0.1" + "#) + .file("src/main.rs", "fn main() {}"); + p.build(); + git2::Repository::init(&p.root()).unwrap(); + + assert_that(p.process(cargo_dir().join("cargo")).arg("package") + .arg("--no-verify").arg("-v"), + execs().with_status(0).with_stdout(format!("\ +{packaging} foo v0.0.1 ([..]) +{archiving} [..] +{archiving} [..] +", packaging = PACKAGING, archiving = ARCHIVING).as_slice())); +});