fix(publish): include explicitly specified .gitignored files and directories (#22790)

This allows explicitly overriding a .gitignore by specifying files and
directories in "include". This does not apply to globs in an include as
files matching those will still be gitignored. Additionally,
individually gitignored files within an included directory will still be
ignored.
This commit is contained in:
David Sherret 2024-03-08 00:46:06 -05:00 committed by GitHub
parent 2c6e9107b6
commit 40089b37c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 69 additions and 7 deletions

View file

@ -326,7 +326,30 @@ impl<TFilter: Fn(WalkEntry) -> bool> FileCollector<TFilter> {
}
let mut maybe_git_ignores = if self.use_gitignore {
Some(GitIgnoreTree::new(Arc::new(deno_runtime::deno_fs::RealFs)))
// Override explicitly specified include paths in the
// .gitignore file. This does not apply to globs because
// that is way too complicated to reason about.
let include_paths = file_patterns
.include
.as_ref()
.map(|include| {
include
.inner()
.iter()
.filter_map(|path_or_pattern| {
if let PathOrPattern::Path(p) = path_or_pattern {
Some(p.clone())
} else {
None
}
})
.collect::<Vec<_>>()
})
.unwrap_or_default();
Some(GitIgnoreTree::new(
Arc::new(deno_runtime::deno_fs::RealFs),
include_paths,
))
} else {
None
};

View file

@ -38,13 +38,19 @@ impl DirGitIgnores {
pub struct GitIgnoreTree {
fs: Arc<dyn deno_runtime::deno_fs::FileSystem>,
ignores: HashMap<PathBuf, Option<Rc<DirGitIgnores>>>,
include_paths: Vec<PathBuf>,
}
impl GitIgnoreTree {
pub fn new(fs: Arc<dyn deno_runtime::deno_fs::FileSystem>) -> Self {
pub fn new(
fs: Arc<dyn deno_runtime::deno_fs::FileSystem>,
// paths that should override what's in the gitignore
include_paths: Vec<PathBuf>,
) -> Self {
Self {
fs,
ignores: Default::default(),
include_paths,
}
}
@ -94,6 +100,16 @@ impl GitIgnoreTree {
for line in text.lines() {
builder.add_line(None, line).ok()?;
}
// override the gitignore contents to include these paths
for path in &self.include_paths {
if let Ok(suffix) = path.strip_prefix(dir_path) {
let suffix = suffix.to_string_lossy().replace('\\', "/");
let _ignore = builder.add_line(None, &format!("!/{}", suffix));
if !suffix.ends_with('/') {
let _ignore = builder.add_line(None, &format!("!/{}/", suffix));
}
}
}
let gitignore = builder.build().ok()?;
Some(Rc::new(gitignore))
});
@ -122,7 +138,7 @@ mod test {
"!file.txt\nignore.txt".into(),
),
]);
let mut ignore_tree = GitIgnoreTree::new(Arc::new(fs));
let mut ignore_tree = GitIgnoreTree::new(Arc::new(fs), Vec::new());
let mut run_test = |path: &str, expected: bool| {
let path = PathBuf::from(path);
let gitignore = ignore_tree

View file

@ -409,7 +409,7 @@ fn ignores_directories() {
}
#[test]
fn not_include_gitignored_file_even_if_matched_in_include() {
fn not_include_gitignored_file_unless_exact_match_in_include() {
let context = publish_context_builder().build();
let temp_dir = context.temp_dir().path();
temp_dir.join("deno.json").write_json(&json!({
@ -417,22 +417,45 @@ fn not_include_gitignored_file_even_if_matched_in_include() {
"version": "1.0.0",
"exports": "./main.ts",
"publish": {
// won't match ignored because it needs to be
// won't match ignored.ts because it needs to be
// unexcluded via a negated glob in exclude
"include": [ "deno.json", "*.ts" ]
"include": [
"deno.json",
"*.ts",
"exact_include.ts",
"sub"
]
}
}));
temp_dir.join(".gitignore").write("ignored.ts");
temp_dir
.join(".gitignore")
.write("ignored.ts\nexact_include.ts\nsub/\nsub/ignored\n/sub_ignored\n");
temp_dir.join("main.ts").write("");
temp_dir.join("ignored.ts").write("");
temp_dir.join("exact_include.ts").write("");
let sub_dir = temp_dir.join("sub");
sub_dir.create_dir_all();
sub_dir.join("sub_included.ts").write("");
sub_dir.join("ignored.ts").write(""); // this one is gitignored
sub_dir.join("ignored").create_dir_all();
sub_dir.join("ignored").join("ignored_also.ts").write("");
let sub_ignored_dir = temp_dir.join("sub_ignored");
sub_ignored_dir.create_dir_all();
sub_ignored_dir.join("sub_ignored.ts").write("");
let output = context.new_command().arg("publish").arg("--dry-run").run();
output.assert_exit_code(0);
let output = output.combined_output();
assert_contains!(output, "main.ts");
// will match this exact match
assert_contains!(output, "exact_include.ts");
// will include this because the sub directory is included
assert_contains!(output, "sub_included.ts");
// it's gitignored
assert_not_contains!(output, "ignored.ts");
assert_not_contains!(output, "ignored_also.ts");
assert_not_contains!(output, "sub_ignored.ts");
}
#[test]