revlog: update the split + transaction test
We add section, increase the amount of comments and simplify some of the
constructs. We are about to build more on top this tests so lets do a small
cleanup first.
use std::fs;
use std::io;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::Path;
const EXECFLAGS: u32 = 0o111;
fn is_executable(path: impl AsRef<Path>) -> Result<bool, io::Error> {
let metadata = fs::metadata(path)?;
let mode = metadata.mode();
Ok(mode & EXECFLAGS != 0)
}
fn make_executable(path: impl AsRef<Path>) -> Result<(), io::Error> {
let mode = fs::metadata(path.as_ref())?.mode();
fs::set_permissions(
path,
fs::Permissions::from_mode((mode & 0o777) | EXECFLAGS),
)?;
Ok(())
}
fn copy_mode(
src: impl AsRef<Path>,
dst: impl AsRef<Path>,
) -> Result<(), io::Error> {
let mode = match fs::symlink_metadata(src) {
Ok(metadata) => metadata.mode(),
Err(e) if e.kind() == io::ErrorKind::NotFound =>
// copymode in python has a more complicated handling of FileNotFound
// error, which we don't need because all it does is applying
// umask, which the OS already does when we mkdir.
{
return Ok(())
}
Err(e) => return Err(e),
};
fs::set_permissions(dst, fs::Permissions::from_mode(mode))?;
Ok(())
}
fn check_exec_impl(path: impl AsRef<Path>) -> Result<bool, io::Error> {
let basedir = path.as_ref().join(".hg");
let cachedir = basedir.join("wcache");
let storedir = basedir.join("store");
if !cachedir.exists() {
// we want to create the 'cache' directory, not the '.hg' one.
// Automatically creating '.hg' directory could silently spawn
// invalid Mercurial repositories. That seems like a bad idea.
fs::create_dir(&cachedir)
.and_then(|()| {
if storedir.exists() {
copy_mode(&storedir, &cachedir)
} else {
copy_mode(&basedir, &cachedir)
}
})
.ok();
}
let leave_file: bool;
let checkdir: &Path;
let checkisexec = cachedir.join("checkisexec");
let checknoexec = cachedir.join("checknoexec");
if cachedir.is_dir() {
// Check if both files already exist in cache and have correct
// permissions. if so, we assume that permissions work.
// If not, we delete the files and try again.
match is_executable(&checkisexec) {
Err(e) if e.kind() == io::ErrorKind::NotFound => (),
Err(e) => return Err(e),
Ok(is_exec) => {
if is_exec {
let noexec_is_exec = match is_executable(&checknoexec) {
Err(e) if e.kind() == io::ErrorKind::NotFound => {
fs::write(&checknoexec, "")?;
is_executable(&checknoexec)?
}
Err(e) => return Err(e),
Ok(exec) => exec,
};
if !noexec_is_exec {
// check-exec is exec and check-no-exec is not exec
return Ok(true);
}
fs::remove_file(&checknoexec)?;
}
fs::remove_file(&checkisexec)?;
}
}
checkdir = &cachedir;
leave_file = true;
} else {
// no cache directory (probably because .hg doesn't exist):
// check directly in `path` and don't leave the temp file behind
checkdir = path.as_ref();
leave_file = false;
};
let tmp_file = tempfile::NamedTempFile::new_in(checkdir)?;
if !is_executable(tmp_file.path())? {
make_executable(tmp_file.path())?;
if is_executable(tmp_file.path())? {
if leave_file {
tmp_file.persist(checkisexec).ok();
}
return Ok(true);
}
}
Ok(false)
}
/// This function is a rust rewrite of [checkexec] function from [posix.py]
/// Returns true if the filesystem supports execute permissions.
pub fn check_exec(path: impl AsRef<Path>) -> bool {
check_exec_impl(path).unwrap_or(false)
}