Skip to content

Commit 70b1607

Browse files
alexcrichtonLuca Bruno
authored and
Luca Bruno
committed
Update how a PathSource is traversed for git repos
This fixes a number of bugs along the way: * Submodules are now recursed into explicitly for packaging, fixing rust-lang#943 * A whitelist has been implemented, fixing rust-lang#880 * Git repos are now always used if there is a package that resides at the root, not just if the current package resides at the root.
1 parent 23ed197 commit 70b1607

File tree

7 files changed

+143
-39
lines changed

7 files changed

+143
-39
lines changed

src/cargo/core/manifest.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub struct Manifest {
2020
links: Option<String>,
2121
warnings: Vec<String>,
2222
exclude: Vec<String>,
23+
include: Vec<String>,
2324
metadata: ManifestMetadata,
2425
}
2526

@@ -398,7 +399,10 @@ impl Show for Target {
398399
impl Manifest {
399400
pub fn new(summary: Summary, targets: Vec<Target>,
400401
target_dir: Path, doc_dir: Path,
401-
build: Vec<String>, exclude: Vec<String>, links: Option<String>,
402+
build: Vec<String>,
403+
exclude: Vec<String>,
404+
include: Vec<String>,
405+
links: Option<String>,
402406
metadata: ManifestMetadata) -> Manifest {
403407
Manifest {
404408
summary: summary,
@@ -408,6 +412,7 @@ impl Manifest {
408412
build: build, // TODO: deprecated, remove
409413
warnings: Vec::new(),
410414
exclude: exclude,
415+
include: include,
411416
links: links,
412417
metadata: metadata,
413418
}
@@ -465,6 +470,10 @@ impl Manifest {
465470
self.exclude.as_slice()
466471
}
467472

473+
pub fn get_include(&self) -> &[String] {
474+
self.include.as_slice()
475+
}
476+
468477
pub fn get_metadata(&self) -> &ManifestMetadata { &self.metadata }
469478

470479
pub fn set_summary(&mut self, summary: Summary) {

src/cargo/ops/cargo_new.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use git2::Config;
99
use util::{GitRepo, HgRepo, CargoResult, human, ChainError, config, internal};
1010
use core::shell::MultiShell;
1111

12-
#[deriving(Copy, Show, PartialEq)]
12+
#[derive(Copy, Show, PartialEq)]
1313
pub enum VersionControl { Git, Hg, NoVcs }
1414

1515
pub struct NewOptions<'a> {

src/cargo/sources/path.rs

Lines changed: 68 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use git2;
66

77
use core::{Package, PackageId, Summary, SourceId, Source, Dependency, Registry};
88
use ops;
9-
use util::{CargoResult, internal, internal_error};
9+
use util::{CargoResult, internal, internal_error, human, ChainError};
1010

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

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

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

89-
let pats = pkg.get_manifest().get_exclude().iter().map(|p| {
90-
Pattern::new(p.as_slice())
91-
}).collect::<Vec<Pattern>>();
92-
93-
Ok(candidates.into_iter().filter(|candidate| {
94-
let relative_path = candidate.path_relative_from(&root).unwrap();
95-
!pats.iter().any(|p| p.matches_path(&relative_path)) &&
96-
candidate.is_file()
97-
}).collect())
89+
// If this package is a git repository, then we really do want to query
90+
// the git repository as it takes into account items such as .gitignore.
91+
// We're not quite sure where the git repository is, however, so we do a
92+
// bit of a probe.
93+
//
94+
// We check all packages in this source that are ancestors of the
95+
// specified package (including the same package) to see if they're at
96+
// the root of the git repository. This isn't always true, but it'll get
97+
// us there most of the time!.
98+
let repo = self.packages.iter()
99+
.map(|pkg| pkg.get_root())
100+
.filter(|path| path.is_ancestor_of(&root))
101+
.filter_map(|path| git2::Repository::open(&path).ok())
102+
.next();
103+
match repo {
104+
Some(repo) => self.list_files_git(pkg, repo, &mut filter),
105+
None => self.list_files_walk(pkg, filter),
106+
}
98107
}
99108

100-
fn list_files_git(&self, pkg: &Package, repo: git2::Repository)
101-
-> CargoResult<Vec<Path>> {
109+
fn list_files_git<F>(&self, pkg: &Package, repo: git2::Repository,
110+
filter: &mut F)
111+
-> CargoResult<Vec<Path>>
112+
where F: FnMut(&Path) -> bool
113+
{
102114
warn!("list_files_git {}", pkg.get_package_id());
103115
let index = try!(repo.index());
104116
let root = match repo.workdir() {
@@ -108,8 +120,7 @@ impl PathSource {
108120
let pkg_path = pkg.get_manifest_path().dir_path();
109121

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

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

131-
// We found a file!
132-
warn!(" found {}", file_path.display());
133-
ret.push(file_path);
143+
// TODO: the `entry` has a mode we should be able to look at instead
144+
// of just calling stat() again
145+
if file_path.is_dir() {
146+
warn!(" found submodule {}", file_path.display());
147+
let rel = file_path.path_relative_from(&root).unwrap();
148+
let rel = try!(rel.as_str().chain_error(|| {
149+
human(format!("invalid utf-8 filename: {}", rel.display()))
150+
}));
151+
let submodule = try!(repo.find_submodule(rel));
152+
let repo = try!(submodule.open());
153+
let files = try!(self.list_files_git(pkg, repo, filter));
154+
ret.extend(files.into_iter());
155+
} else if (*filter)(&file_path) {
156+
// We found a file!
157+
warn!(" found {}", file_path.display());
158+
ret.push(file_path);
159+
}
134160
}
135161
Ok(ret)
136162
}
137163

138-
fn list_files_walk(&self, pkg: &Package) -> CargoResult<Vec<Path>> {
164+
fn list_files_walk<F>(&self, pkg: &Package, mut filter: F)
165+
-> CargoResult<Vec<Path>>
166+
where F: FnMut(&Path) -> bool
167+
{
139168
let mut ret = Vec::new();
140169
for pkg in self.packages.iter().filter(|p| *p == pkg) {
141170
let loc = pkg.get_manifest_path().dir_path();
142-
try!(walk(&loc, &mut ret, true));
171+
try!(walk(&loc, &mut ret, true, &mut filter));
143172
}
144173
return Ok(ret);
145174

146-
fn walk(path: &Path, ret: &mut Vec<Path>,
147-
is_root: bool) -> CargoResult<()> {
175+
fn walk<F>(path: &Path, ret: &mut Vec<Path>,
176+
is_root: bool, filter: &mut F) -> CargoResult<()>
177+
where F: FnMut(&Path) -> bool
178+
{
148179
if !path.is_dir() {
149-
ret.push(path.clone());
180+
if (*filter)(path) {
181+
ret.push(path.clone());
182+
}
150183
return Ok(())
151184
}
152185
// Don't recurse into any sub-packages that we have
@@ -158,7 +191,7 @@ impl PathSource {
158191
(true, Some("Cargo.lock")) => continue,
159192
_ => {}
160193
}
161-
try!(walk(dir, ret, false));
194+
try!(walk(dir, ret, false, filter));
162195
}
163196
return Ok(())
164197
}

src/cargo/util/toml.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ pub struct TomlProject {
261261
build: Option<BuildCommand>, // TODO: `String` instead
262262
links: Option<String>,
263263
exclude: Option<Vec<String>>,
264+
include: Option<Vec<String>>,
264265

265266
// package metadata
266267
description: Option<String>,
@@ -508,6 +509,7 @@ impl TomlManifest {
508509
}
509510

510511
let exclude = project.exclude.clone().unwrap_or(Vec::new());
512+
let include = project.include.clone().unwrap_or(Vec::new());
511513

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

@@ -531,6 +533,7 @@ impl TomlManifest {
531533
layout.root.join("doc"),
532534
old_build,
533535
exclude,
536+
include,
534537
project.links.clone(),
535538
metadata);
536539
if used_deprecated_lib {

src/doc/crates-io.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,18 @@ exclude = [
144144
```
145145

146146
The syntax of each element in this array is what
147-
[rust-lang/glob](https://github.com/rust-lang/glob) accepts.
147+
[rust-lang/glob](https://github.com/rust-lang/glob) accepts. If you'd rather
148+
roll with a whitelist instead of a blacklist, Cargo also supports an `include`
149+
key:
150+
151+
```toml
152+
[package]
153+
# ...
154+
include = [
155+
"**/*.rs",
156+
"Cargo.toml",
157+
]
158+
```
148159

149160
## Uploading the crate
150161

tests/test_cargo_package.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,51 @@ test!(package_verification {
194194
compiling = COMPILING,
195195
dir = p.url()).as_slice()));
196196
});
197+
198+
test!(exclude {
199+
let p = project("foo")
200+
.file("Cargo.toml", r#"
201+
[project]
202+
name = "foo"
203+
version = "0.0.1"
204+
authors = []
205+
exclude = ["*.txt"]
206+
"#)
207+
.file("src/main.rs", r#"
208+
fn main() { println!("hello"); }
209+
"#)
210+
.file("bar.txt", "")
211+
.file("src/bar.txt", "");
212+
213+
assert_that(p.cargo_process("package").arg("--no-verify").arg("-v"),
214+
execs().with_status(0).with_stdout(format!("\
215+
{packaging} foo v0.0.1 ([..])
216+
{archiving} [..]
217+
{archiving} [..]
218+
", packaging = PACKAGING, archiving = ARCHIVING).as_slice()));
219+
});
220+
221+
test!(include {
222+
let p = project("foo")
223+
.file("Cargo.toml", r#"
224+
[project]
225+
name = "foo"
226+
version = "0.0.1"
227+
authors = []
228+
exclude = ["*.txt"]
229+
include = ["foo.txt", "**/*.rs", "Cargo.toml"]
230+
"#)
231+
.file("foo.txt", "")
232+
.file("src/main.rs", r#"
233+
fn main() { println!("hello"); }
234+
"#)
235+
.file("src/bar.txt", ""); // should be ignored when packaging
236+
237+
assert_that(p.cargo_process("package").arg("--no-verify").arg("-v"),
238+
execs().with_status(0).with_stdout(format!("\
239+
{packaging} foo v0.0.1 ([..])
240+
{archiving} [..]
241+
{archiving} [..]
242+
{archiving} [..]
243+
", packaging = PACKAGING, archiving = ARCHIVING).as_slice()));
244+
});

tests/test_cargo_registry.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ test!(bad_license_file {
497497
.file("src/main.rs", r#"
498498
fn main() {}
499499
"#);
500-
assert_that(p.cargo_process("publish"),
500+
assert_that(p.cargo_process("publish").arg("-v"),
501501
execs().with_status(101)
502502
.with_stderr("\
503503
the license file `foo` does not exist"));

0 commit comments

Comments
 (0)