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

Update how a PathSource is traversed for git repos #1131

Merged
merged 1 commit into from
Jan 8, 2015
Merged
Show file tree
Hide file tree
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
11 changes: 10 additions & 1 deletion src/cargo/core/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub struct Manifest {
links: Option<String>,
warnings: Vec<String>,
exclude: Vec<String>,
include: Vec<String>,
metadata: ManifestMetadata,
}

Expand Down Expand Up @@ -412,7 +413,10 @@ impl Show for Target {
impl Manifest {
pub fn new(summary: Summary, targets: Vec<Target>,
target_dir: Path, doc_dir: Path,
build: Vec<String>, exclude: Vec<String>, links: Option<String>,
build: Vec<String>,
exclude: Vec<String>,
include: Vec<String>,
links: Option<String>,
metadata: ManifestMetadata) -> Manifest {
Manifest {
summary: summary,
Expand All @@ -422,6 +426,7 @@ impl Manifest {
build: build, // TODO: deprecated, remove
warnings: Vec::new(),
exclude: exclude,
include: include,
links: links,
metadata: metadata,
}
Expand Down Expand Up @@ -479,6 +484,10 @@ impl Manifest {
self.exclude.as_slice()
}

pub fn get_include(&self) -> &[String] {
self.include.as_slice()
}

pub fn get_metadata(&self) -> &ManifestMetadata { &self.metadata }

pub fn set_summary(&mut self, summary: Summary) {
Expand Down
2 changes: 1 addition & 1 deletion src/cargo/ops/cargo_new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use git2::Config;
use util::{GitRepo, HgRepo, CargoResult, human, ChainError, config, internal};
use core::shell::MultiShell;

#[deriving(Copy, Show, PartialEq)]
#[derive(Copy, Show, PartialEq)]
pub enum VersionControl { Git, Hg, NoVcs }

pub struct NewOptions<'a> {
Expand Down
103 changes: 68 additions & 35 deletions src/cargo/sources/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use git2;

use core::{Package, PackageId, Summary, SourceId, Source, Dependency, Registry};
use ops;
use util::{CargoResult, internal, internal_error};
use util::{CargoResult, internal, internal_error, human, ChainError};

pub struct PathSource {
id: SourceId,
Expand Down Expand Up @@ -71,34 +71,46 @@ impl PathSource {
pub fn list_files(&self, pkg: &Package) -> CargoResult<Vec<Path>> {
let root = pkg.get_manifest_path().dir_path();

// Check whether the package itself is a git repository.
let candidates = match git2::Repository::open(&root) {
Ok(repo) => try!(self.list_files_git(pkg, repo)),
let exclude = pkg.get_manifest().get_exclude().iter().map(|p| {
Pattern::new(p.as_slice())
}).collect::<Vec<Pattern>>();
let include = pkg.get_manifest().get_include().iter().map(|p| {
Pattern::new(p.as_slice())
}).collect::<Vec<Pattern>>();

// If not, check whether the package is in a sub-directory of the main repository.
Err(..) if self.path.is_ancestor_of(&root) => {
match git2::Repository::open(&self.path) {
Ok(repo) => try!(self.list_files_git(pkg, repo)),
_ => try!(self.list_files_walk(pkg))
}
let mut filter = |&mut: p: &Path| {
let relative_path = p.path_relative_from(&root).unwrap();
include.iter().any(|p| p.matches_path(&relative_path)) || {
include.len() == 0 &&
!exclude.iter().any(|p| p.matches_path(&relative_path))
}
// If neither is true, fall back to walking the filesystem.
_ => try!(self.list_files_walk(pkg))
};

let pats = pkg.get_manifest().get_exclude().iter().map(|p| {
Pattern::new(p.as_slice())
}).collect::<Vec<Pattern>>();

Ok(candidates.into_iter().filter(|candidate| {
let relative_path = candidate.path_relative_from(&root).unwrap();
!pats.iter().any(|p| p.matches_path(&relative_path)) &&
candidate.is_file()
}).collect())
// If this package is a git repository, then we really do want to query
// the git repository as it takes into account items such as .gitignore.
// We're not quite sure where the git repository is, however, so we do a
// bit of a probe.
//
// We check all packages in this source that are ancestors of the
// specified package (including the same package) to see if they're at
// the root of the git repository. This isn't always true, but it'll get
// us there most of the time!.
let repo = self.packages.iter()
.map(|pkg| pkg.get_root())
.filter(|path| path.is_ancestor_of(&root))
.filter_map(|path| git2::Repository::open(&path).ok())
.next();
match repo {
Some(repo) => self.list_files_git(pkg, repo, &mut filter),
None => self.list_files_walk(pkg, filter),
}
}

fn list_files_git(&self, pkg: &Package, repo: git2::Repository)
-> CargoResult<Vec<Path>> {
fn list_files_git<F>(&self, pkg: &Package, repo: git2::Repository,
filter: &mut F)
-> CargoResult<Vec<Path>>
where F: FnMut(&Path) -> bool
{
warn!("list_files_git {}", pkg.get_package_id());
let index = try!(repo.index());
let root = match repo.workdir() {
Expand All @@ -108,8 +120,7 @@ impl PathSource {
let pkg_path = pkg.get_manifest_path().dir_path();

let mut ret = Vec::new();
'outer: for i in range(0, index.len()) {
let entry = match index.get(i) { Some(e) => e, None => continue };
'outer: for entry in index.iter() {
let fname = entry.path.as_bytes_no_nul();
let file_path = root.join(fname);

Expand All @@ -123,30 +134,52 @@ impl PathSource {
// Filter out sub-packages of this package
for other_pkg in self.packages.iter().filter(|p| *p != pkg) {
let other_path = other_pkg.get_manifest_path().dir_path();
if pkg_path.is_ancestor_of(&other_path) && other_path.is_ancestor_of(&file_path) {
if pkg_path.is_ancestor_of(&other_path) &&
other_path.is_ancestor_of(&file_path) {
continue 'outer;
}
}

// We found a file!
warn!(" found {}", file_path.display());
ret.push(file_path);
// TODO: the `entry` has a mode we should be able to look at instead
// of just calling stat() again
if file_path.is_dir() {
warn!(" found submodule {}", file_path.display());
let rel = file_path.path_relative_from(&root).unwrap();
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());
} else if (*filter)(&file_path) {
// We found a file!
warn!(" found {}", file_path.display());
ret.push(file_path);
}
}
Ok(ret)
}

fn list_files_walk(&self, pkg: &Package) -> CargoResult<Vec<Path>> {
fn list_files_walk<F>(&self, pkg: &Package, mut filter: F)
-> CargoResult<Vec<Path>>
where F: FnMut(&Path) -> bool
{
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));
try!(walk(&loc, &mut ret, true, &mut filter));
}
return Ok(ret);

fn walk(path: &Path, ret: &mut Vec<Path>,
is_root: bool) -> CargoResult<()> {
fn walk<F>(path: &Path, ret: &mut Vec<Path>,
is_root: bool, filter: &mut F) -> CargoResult<()>
where F: FnMut(&Path) -> bool
{
if !path.is_dir() {
ret.push(path.clone());
if (*filter)(path) {
ret.push(path.clone());
}
return Ok(())
}
// Don't recurse into any sub-packages that we have
Expand All @@ -158,7 +191,7 @@ impl PathSource {
(true, Some("Cargo.lock")) => continue,
_ => {}
}
try!(walk(dir, ret, false));
try!(walk(dir, ret, false, filter));
}
return Ok(())
}
Expand Down
3 changes: 3 additions & 0 deletions src/cargo/util/toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ pub struct TomlProject {
build: Option<BuildCommand>, // TODO: `String` instead
links: Option<String>,
exclude: Option<Vec<String>>,
include: Option<Vec<String>>,

// package metadata
description: Option<String>,
Expand Down Expand Up @@ -508,6 +509,7 @@ impl TomlManifest {
}

let exclude = project.exclude.clone().unwrap_or(Vec::new());
let include = project.include.clone().unwrap_or(Vec::new());

let has_old_build = old_build.len() >= 1;

Expand All @@ -531,6 +533,7 @@ impl TomlManifest {
layout.root.join("doc"),
old_build,
exclude,
include,
project.links.clone(),
metadata);
if used_deprecated_lib {
Expand Down
13 changes: 12 additions & 1 deletion src/doc/crates-io.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,18 @@ exclude = [
```

The syntax of each element in this array is what
[rust-lang/glob](https://github.com/rust-lang/glob) accepts.
[rust-lang/glob](https://github.com/rust-lang/glob) accepts. If you'd rather
roll with a whitelist instead of a blacklist, Cargo also supports an `include`
key:

```toml
[package]
# ...
include = [
"**/*.rs",
"Cargo.toml",
]
```

## Uploading the crate

Expand Down
48 changes: 48 additions & 0 deletions tests/test_cargo_package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,51 @@ test!(package_verification {
compiling = COMPILING,
dir = p.url()).as_slice()));
});

test!(exclude {
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.0.1"
authors = []
exclude = ["*.txt"]
"#)
.file("src/main.rs", r#"
fn main() { println!("hello"); }
"#)
.file("bar.txt", "")
.file("src/bar.txt", "");

assert_that(p.cargo_process("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()));
});

test!(include {
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.0.1"
authors = []
exclude = ["*.txt"]
include = ["foo.txt", "**/*.rs", "Cargo.toml"]
"#)
.file("foo.txt", "")
.file("src/main.rs", r#"
fn main() { println!("hello"); }
"#)
.file("src/bar.txt", ""); // should be ignored when packaging

assert_that(p.cargo_process("package").arg("--no-verify").arg("-v"),
execs().with_status(0).with_stdout(format!("\
{packaging} foo v0.0.1 ([..])
{archiving} [..]
{archiving} [..]
{archiving} [..]
", packaging = PACKAGING, archiving = ARCHIVING).as_slice()));
});
2 changes: 1 addition & 1 deletion tests/test_cargo_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ test!(bad_license_file {
.file("src/main.rs", r#"
fn main() {}
"#);
assert_that(p.cargo_process("publish"),
assert_that(p.cargo_process("publish").arg("-v"),
execs().with_status(101)
.with_stderr("\
the license file `foo` does not exist"));
Expand Down