From 40089b37c0a98fdcfc5b44c5ecb2c390cd16e66c Mon Sep 17 00:00:00 2001 From: David Sherret Date: Fri, 8 Mar 2024 00:46:06 -0500 Subject: [PATCH] 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. --- cli/util/fs.rs | 25 +++++++++++++++++++++++- cli/util/gitignore.rs | 20 +++++++++++++++++-- tests/integration/publish_tests.rs | 31 ++++++++++++++++++++++++++---- 3 files changed, 69 insertions(+), 7 deletions(-) diff --git a/cli/util/fs.rs b/cli/util/fs.rs index f6354097a9..352e099994 100644 --- a/cli/util/fs.rs +++ b/cli/util/fs.rs @@ -326,7 +326,30 @@ impl bool> FileCollector { } 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::>() + }) + .unwrap_or_default(); + Some(GitIgnoreTree::new( + Arc::new(deno_runtime::deno_fs::RealFs), + include_paths, + )) } else { None }; diff --git a/cli/util/gitignore.rs b/cli/util/gitignore.rs index da9065494c..f4185aa0d9 100644 --- a/cli/util/gitignore.rs +++ b/cli/util/gitignore.rs @@ -38,13 +38,19 @@ impl DirGitIgnores { pub struct GitIgnoreTree { fs: Arc, ignores: HashMap>>, + include_paths: Vec, } impl GitIgnoreTree { - pub fn new(fs: Arc) -> Self { + pub fn new( + fs: Arc, + // paths that should override what's in the gitignore + include_paths: Vec, + ) -> 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 diff --git a/tests/integration/publish_tests.rs b/tests/integration/publish_tests.rs index 53bb6a376c..28046d0477 100644 --- a/tests/integration/publish_tests.rs +++ b/tests/integration/publish_tests.rs @@ -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]