diff --git a/rust/hg-core/src/operations/list_tracked_files.rs b/rust/hg-core/src/operations/list_tracked_files.rs --- a/rust/hg-core/src/operations/list_tracked_files.rs +++ b/rust/hg-core/src/operations/list_tracked_files.rs @@ -25,11 +25,12 @@ impl Dirstate { pub fn new(repo: &Repo) -> Result { - let mut content = repo.hg_vfs().read("dirstate")?; + let hg_vfs = repo.hg_vfs_for_dirstate()?; + let mut content = hg_vfs.read("dirstate")?; let v2_metadata = if repo.has_dirstate_v2() { let docket = read_docket(&content)?; let meta = docket.tree_metadata().to_vec(); - content = repo.hg_vfs().read(docket.data_filename())?; + content = hg_vfs.read(docket.data_filename())?; Some(meta) } else { None diff --git a/rust/hg-core/src/repo.rs b/rust/hg-core/src/repo.rs --- a/rust/hg-core/src/repo.rs +++ b/rust/hg-core/src/repo.rs @@ -227,7 +227,31 @@ /// For accessing repository files (in `.hg`), except for the store /// (`.hg/store`). - pub fn hg_vfs(&self) -> Vfs<'_> { + fn hg_vfs(&self) -> Vfs<'_> { + Vfs { base: &self.dot_hg } + } + + fn has_sparse(&self) -> bool { + self.requirements.contains(requirements::SPARSE_REQUIREMENT) + } + + fn check_not_sparse(&self) -> Result<(), HgError> { + if self.has_sparse() { + Err(HgError::unsupported(format!( + "repository uses a sparse working copy, this is not supported by rhg", + ))) + } else { + Ok(()) + } + } + + /// For accessing the dirstate. + pub fn hg_vfs_for_dirstate(&self) -> Result, HgError> { + let () = self.check_not_sparse()?; + Ok(Vfs { base: &self.dot_hg }) + } + + pub fn hg_vfs_not_using_dirstate(&self) -> Vfs<'_> { Vfs { base: &self.dot_hg } } @@ -237,10 +261,19 @@ } /// For accessing the working copy - pub fn working_directory_vfs(&self) -> Vfs<'_> { - Vfs { + pub fn working_directory_vfs(&self) -> Result, HgError> { + let () = self.check_not_sparse()?; + Ok(Vfs { base: &self.working_directory, - } + }) + } + + pub fn is_a_subrepo(&self) -> bool { + (Vfs { + base: &self.working_directory, + }) + .join(".hgsub") + .exists() } pub fn has_dirstate_v2(&self) -> bool { diff --git a/rust/hg-core/src/requirements.rs b/rust/hg-core/src/requirements.rs --- a/rust/hg-core/src/requirements.rs +++ b/rust/hg-core/src/requirements.rs @@ -88,6 +88,9 @@ // When it starts writing to the repository, it’ll need to either keep the // persistent nodemap up to date or remove this entry: NODEMAP_REQUIREMENT, + // We don't understand sparse working copies, but we understand + // how to use the store because it's not affected by sparse. + SPARSE_REQUIREMENT, ]; // Copied from mercurial/requirements.py: diff --git a/rust/rhg/src/blackbox.rs b/rust/rhg/src/blackbox.rs --- a/rust/rhg/src/blackbox.rs +++ b/rust/rhg/src/blackbox.rs @@ -133,11 +133,13 @@ pid, message ); - let result = - hg::logging::LogFile::new(self.repo.hg_vfs(), "blackbox.log") - .max_size(Some(self.max_size)) - .max_files(self.max_files) - .write(&line); + let result = hg::logging::LogFile::new( + self.repo.hg_vfs_not_using_dirstate(), + "blackbox.log", + ) + .max_size(Some(self.max_size)) + .max_files(self.max_files) + .write(&line); match result { Ok(()) => {} Err(_io_error) => { diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -190,7 +190,7 @@ list_ignored: display_states.ignored, collect_traversed_dirs: false, }; - let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded + let ignore_file = repo.working_directory_vfs()?.join(".hgignore"); // TODO hardcoded let (mut ds_status, pattern_warnings) = dmap.status( &AlwaysMatcher, repo.working_directory_path().to_owned(), @@ -311,6 +311,6 @@ let contents_in_p1 = filelog_entry.data()?; let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion"); - let fs_contents = repo.working_directory_vfs().read(fs_path)?; + let fs_contents = repo.working_directory_vfs()?.read(fs_path)?; return Ok(contents_in_p1 != &*fs_contents); } diff --git a/rust/rhg/src/main.rs b/rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs +++ b/rust/rhg/src/main.rs @@ -104,7 +104,7 @@ if let Ok(repo) = repo { // We don't support subrepos, fallback if the subrepos file is present - if repo.working_directory_vfs().join(".hgsub").exists() { + if repo.is_a_subrepo() { let msg = "subrepos (.hgsub is present)"; return Err(CommandError::unsupported(msg)); } @@ -588,7 +588,8 @@ } } -const SUPPORTED_EXTENSIONS: &[&[u8]] = &[b"blackbox", b"share"]; +const SUPPORTED_EXTENSIONS: &[&[u8]] = + &[b"blackbox", b"share", b"sparse", b"narrow"]; fn check_extensions(config: &Config) -> Result<(), CommandError> { let enabled = config.get_section_keys(b"extensions"); diff --git a/tests/test-rhg-sparse.t b/tests/test-rhg-sparse.t new file mode 100644 --- /dev/null +++ b/tests/test-rhg-sparse.t @@ -0,0 +1,33 @@ +#require rhg + + $ NO_FALLBACK="env RHG_ON_UNSUPPORTED=abort" + +Even though sparse working copy is not supported, the commands +that operate on the store still work even if the working copy is sparse. + + $ cd "$TESTTMP" + $ hg init repo-sparse + $ cd repo-sparse + $ cat > .hg/hgrc < [extensions] + > sparse= + > EOF + + $ echo a > show + $ echo x > hide + $ hg ci -Aqm 'initial' + $ hg debugsparse --include 'show' + $ ls -A + .hg + show + + $ tip=$(hg log -r . --template '{node}') + $ $NO_FALLBACK rhg files -r "$tip" + hide + show + $ $NO_FALLBACK rhg files + unsupported feature: repository uses a sparse working copy, this is not supported by rhg + [252] + + $ $NO_FALLBACK rhg cat -r "$tip" hide + x