From bb1f30f88e5f6ad5a4bc9306b3bc156a5cb7b4c9 Mon Sep 17 00:00:00 2001 From: Matt Kulukundis Date: Sat, 22 Jun 2024 05:22:18 -0400 Subject: [PATCH] Support .jj as well as .git - Allow `.jj` dirs to count as vcs directories. - Simplify the control flow around resolving info/exclude --- crates/ignore/src/dir.rs | 56 +++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/crates/ignore/src/dir.rs b/crates/ignore/src/dir.rs index b302943a..309906ff 100644 --- a/crates/ignore/src/dir.rs +++ b/crates/ignore/src/dir.rs @@ -212,7 +212,7 @@ impl Ignore { igtmp.absolute_base = Some(absolute_base.clone()); igtmp.has_git = if self.0.opts.require_git && self.0.opts.git_ignore { - parent.join(".git").exists() + parent.join(".git").exists() || parent.join(".jj").exists() } else { false }; @@ -244,15 +244,6 @@ impl Ignore { /// Like add_child, but takes a full path and returns an IgnoreInner. fn add_child_path(&self, dir: &Path) -> (IgnoreInner, Option) { - let git_type = if self.0.opts.require_git - && (self.0.opts.git_ignore || self.0.opts.git_exclude) - { - dir.join(".git").metadata().ok().map(|md| md.file_type()) - } else { - None - }; - let has_git = git_type.map(|_| true).unwrap_or(false); - let mut errs = PartialErrorBuilder::default(); let custom_ig_matcher = if self.0.custom_ignore_filenames.is_empty() { Gitignore::empty() @@ -290,10 +281,28 @@ impl Ignore { errs.maybe_push(err); m }; + + let mut git_dir = dir.join(".git"); + let mut git_type: Option = None; + if self.0.opts.require_git { + git_type = git_dir.metadata().ok().map(|md| md.file_type()); + if git_type.is_none() { + let jj_internal_git_dir = dir.join(".jj/repo/store/git"); + let jj_type = jj_internal_git_dir + .metadata() + .ok() + .map(|md| md.file_type()); + if jj_type.is_some() { + git_dir = jj_internal_git_dir; + git_type = jj_type; + } + } + } + let gi_exclude_matcher = if !self.0.opts.git_exclude { Gitignore::empty() } else { - match resolve_git_commondir(dir, git_type) { + match resolve_git_commondir(git_dir, git_type) { Ok(git_dir) => { let (m, err) = create_gitignore( &dir, @@ -325,7 +334,7 @@ impl Ignore { git_global_matcher: self.0.git_global_matcher.clone(), git_ignore_matcher: gi_matcher, git_exclude_matcher: gi_exclude_matcher, - has_git, + has_git: git_type.is_some(), opts: self.0.opts, }; (ig, errs.into_error_option()) @@ -829,24 +838,22 @@ pub(crate) fn create_gitignore>( /// /// Some I/O errors are ignored. fn resolve_git_commondir( - dir: &Path, + git_dir: PathBuf, git_type: Option, ) -> Result> { - let git_dir_path = || dir.join(".git"); - let git_dir = git_dir_path(); if !git_type.map_or(false, |ft| ft.is_file()) { return Ok(git_dir); } - let file = match File::open(git_dir) { + let file = match File::open(&git_dir) { Ok(file) => io::BufReader::new(file), Err(err) => { - return Err(Some(Error::Io(err).with_path(git_dir_path()))); + return Err(Some(Error::Io(err).with_path(git_dir))); } }; let dot_git_line = match file.lines().next() { Some(Ok(line)) => line, Some(Err(err)) => { - return Err(Some(Error::Io(err).with_path(git_dir_path()))); + return Err(Some(Error::Io(err).with_path(git_dir))); } None => return Err(None), }; @@ -943,6 +950,19 @@ mod tests { assert!(ig.matched("baz", false).is_none()); } + #[test] + fn gitignore_with_jj() { + let td = tmpdir(); + mkdirp(td.path().join(".jj/repo/store/git")); + wfile(td.path().join(".gitignore"), "foo\n!bar"); + + let (ig, err) = IgnoreBuilder::new().build().add_child(td.path()); + assert!(err.is_none()); + assert!(ig.matched("foo", false).is_ignore()); + assert!(ig.matched("bar", false).is_whitelist()); + assert!(ig.matched("baz", false).is_none()); + } + #[test] fn gitignore_no_git() { let td = tmpdir();