|
|
use std::{
|
|
|
cell::Cell,
|
|
|
fs::File,
|
|
|
io::Error,
|
|
|
os::fd::{AsRawFd, FromRawFd},
|
|
|
path::{Path, PathBuf},
|
|
|
};
|
|
|
|
|
|
use cpython::{
|
|
|
ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyInt, PyObject,
|
|
|
PyResult, PyTuple, Python, PythonObject, ToPyObject,
|
|
|
};
|
|
|
use hg::{
|
|
|
errors::{HgError, IoResultExt},
|
|
|
exit_codes,
|
|
|
utils::files::{get_bytes_from_path, get_path_from_bytes},
|
|
|
vfs::{Vfs, VfsFile},
|
|
|
};
|
|
|
|
|
|
/// Wrapper around a Python VFS object to call back into Python from `hg-core`.
|
|
|
pub struct PyVfs {
|
|
|
inner: PyObject,
|
|
|
base: PathBuf,
|
|
|
}
|
|
|
|
|
|
impl Clone for PyVfs {
|
|
|
fn clone(&self) -> Self {
|
|
|
let gil = &Python::acquire_gil();
|
|
|
let py = gil.python();
|
|
|
Self {
|
|
|
inner: self.inner.clone_ref(py),
|
|
|
base: self.base.clone(),
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl PyVfs {
|
|
|
pub fn new(
|
|
|
_py: Python,
|
|
|
py_vfs: PyObject,
|
|
|
base: PathBuf,
|
|
|
) -> PyResult<Self> {
|
|
|
Ok(Self {
|
|
|
inner: py_vfs,
|
|
|
base,
|
|
|
})
|
|
|
}
|
|
|
|
|
|
fn inner_open(
|
|
|
&self,
|
|
|
filename: &Path,
|
|
|
create: bool,
|
|
|
check_ambig: bool,
|
|
|
atomic_temp: bool,
|
|
|
write: bool,
|
|
|
) -> Result<(File, Option<PathBuf>), HgError> {
|
|
|
let gil = &Python::acquire_gil();
|
|
|
let py = gil.python();
|
|
|
let mode = if atomic_temp {
|
|
|
PyBytes::new(py, b"w")
|
|
|
} else if create {
|
|
|
PyBytes::new(py, b"w+")
|
|
|
} else if write {
|
|
|
PyBytes::new(py, b"r+")
|
|
|
} else {
|
|
|
PyBytes::new(py, b"rb")
|
|
|
};
|
|
|
let res = self.inner.call(
|
|
|
py,
|
|
|
(
|
|
|
PyBytes::new(py, &get_bytes_from_path(filename)),
|
|
|
mode,
|
|
|
atomic_temp,
|
|
|
check_ambig,
|
|
|
),
|
|
|
None,
|
|
|
);
|
|
|
match res {
|
|
|
Ok(tup) => {
|
|
|
let tup = tup
|
|
|
.extract::<PyTuple>(py)
|
|
|
.map_err(|e| vfs_error("vfs did not return a tuple", e))?;
|
|
|
let fileno = tup.get_item(py, 0).extract(py).map_err(|e| {
|
|
|
vfs_error("vfs did not return a valid fileno", e)
|
|
|
})?;
|
|
|
let temp_name = tup.get_item(py, 1);
|
|
|
// Safety: this must be a valid owned file descriptor, and
|
|
|
// Python has just given it to us, it will only exist here now
|
|
|
let file = unsafe { File::from_raw_fd(fileno) };
|
|
|
let temp_name = if atomic_temp {
|
|
|
Some(
|
|
|
get_path_from_bytes(
|
|
|
temp_name
|
|
|
.extract::<PyBytes>(py)
|
|
|
.map_err(|e| vfs_error("invalid tempname", e))?
|
|
|
.data(py),
|
|
|
)
|
|
|
.to_owned(),
|
|
|
)
|
|
|
} else {
|
|
|
None
|
|
|
};
|
|
|
Ok((file, temp_name))
|
|
|
}
|
|
|
Err(mut e) => {
|
|
|
// TODO surely there is a better way of comparing
|
|
|
if e.instance(py).get_type(py).name(py) == "FileNotFoundError"
|
|
|
{
|
|
|
return Err(HgError::IoError {
|
|
|
error: Error::new(
|
|
|
std::io::ErrorKind::NotFound,
|
|
|
e.instance(py).to_string(),
|
|
|
),
|
|
|
context: hg::errors::IoErrorContext::ReadingFile(
|
|
|
filename.to_owned(),
|
|
|
),
|
|
|
});
|
|
|
}
|
|
|
Err(vfs_error("failed to call opener", e))
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
fn vfs_error(reason: impl Into<String>, mut error: PyErr) -> HgError {
|
|
|
let gil = &Python::acquire_gil();
|
|
|
let py = gil.python();
|
|
|
HgError::abort(
|
|
|
format!("{}: {}", reason.into(), error.instance(py)),
|
|
|
exit_codes::ABORT,
|
|
|
None,
|
|
|
)
|
|
|
}
|
|
|
|
|
|
py_class!(pub class PyFile |py| {
|
|
|
data number: Cell<i32>;
|
|
|
|
|
|
def fileno(&self) -> PyResult<PyInt> {
|
|
|
Ok(self.number(py).get().to_py_object(py))
|
|
|
}
|
|
|
});
|
|
|
|
|
|
impl Vfs for PyVfs {
|
|
|
fn open(&self, filename: &Path) -> Result<VfsFile, HgError> {
|
|
|
self.inner_open(filename, false, false, false, false)
|
|
|
.map(|(f, _)| VfsFile::normal(f, filename.to_owned()))
|
|
|
}
|
|
|
|
|
|
fn open_write(&self, filename: &Path) -> Result<VfsFile, HgError> {
|
|
|
self.inner_open(filename, false, false, false, true)
|
|
|
.map(|(f, _)| VfsFile::normal(f, filename.to_owned()))
|
|
|
}
|
|
|
|
|
|
fn open_check_ambig(&self, filename: &Path) -> Result<VfsFile, HgError> {
|
|
|
self.inner_open(filename, false, true, false, true)
|
|
|
.map(|(f, _)| VfsFile::normal(f, filename.to_owned()))
|
|
|
}
|
|
|
|
|
|
fn create(
|
|
|
&self,
|
|
|
filename: &Path,
|
|
|
check_ambig: bool,
|
|
|
) -> Result<VfsFile, HgError> {
|
|
|
self.inner_open(filename, true, check_ambig, false, true)
|
|
|
.map(|(f, _)| VfsFile::normal(f, filename.to_owned()))
|
|
|
}
|
|
|
|
|
|
fn create_atomic(
|
|
|
&self,
|
|
|
filename: &Path,
|
|
|
check_ambig: bool,
|
|
|
) -> Result<VfsFile, HgError> {
|
|
|
self.inner_open(filename, true, false, true, true).map(
|
|
|
|(fp, temp_name)| {
|
|
|
VfsFile::Atomic(hg::vfs::AtomicFile::from_file(
|
|
|
fp,
|
|
|
check_ambig,
|
|
|
temp_name.expect("temp name should exist"),
|
|
|
filename.to_owned(),
|
|
|
))
|
|
|
},
|
|
|
)
|
|
|
}
|
|
|
|
|
|
fn file_size(&self, file: &VfsFile) -> Result<u64, HgError> {
|
|
|
let gil = &Python::acquire_gil();
|
|
|
let py = gil.python();
|
|
|
let raw_fd = file.as_raw_fd();
|
|
|
let py_fd = PyFile::create_instance(py, Cell::new(raw_fd))
|
|
|
.expect("create_instance cannot fail");
|
|
|
let fstat = self
|
|
|
.inner
|
|
|
.call_method(py, "fstat", (py_fd,), None)
|
|
|
.map_err(|e| {
|
|
|
vfs_error(format!("failed to fstat fd '{}'", raw_fd), e)
|
|
|
})?;
|
|
|
fstat
|
|
|
.getattr(py, "st_size")
|
|
|
.map(|v| {
|
|
|
v.extract(py).map_err(|e| {
|
|
|
vfs_error(format!("invalid size for fd '{}'", raw_fd), e)
|
|
|
})
|
|
|
})
|
|
|
.map_err(|e| {
|
|
|
vfs_error(format!("failed to get size of fd '{}'", raw_fd), e)
|
|
|
})?
|
|
|
}
|
|
|
|
|
|
fn exists(&self, filename: &Path) -> bool {
|
|
|
let gil = &Python::acquire_gil();
|
|
|
let py = gil.python();
|
|
|
self.inner
|
|
|
.call_method(
|
|
|
py,
|
|
|
"exists",
|
|
|
(PyBytes::new(py, &get_bytes_from_path(filename)),),
|
|
|
None,
|
|
|
)
|
|
|
.unwrap_or_else(|_| false.into_py_object(py).into_object())
|
|
|
.extract(py)
|
|
|
.unwrap()
|
|
|
}
|
|
|
|
|
|
fn unlink(&self, filename: &Path) -> Result<(), HgError> {
|
|
|
let gil = &Python::acquire_gil();
|
|
|
let py = gil.python();
|
|
|
if let Err(e) = self.inner.call_method(
|
|
|
py,
|
|
|
"unlink",
|
|
|
(PyBytes::new(py, &get_bytes_from_path(filename)),),
|
|
|
None,
|
|
|
) {
|
|
|
return Err(vfs_error(
|
|
|
format!("failed to unlink '{}'", filename.display()),
|
|
|
e,
|
|
|
));
|
|
|
}
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
fn rename(
|
|
|
&self,
|
|
|
from: &Path,
|
|
|
to: &Path,
|
|
|
check_ambig: bool,
|
|
|
) -> Result<(), HgError> {
|
|
|
let gil = &Python::acquire_gil();
|
|
|
let py = gil.python();
|
|
|
let kwargs = PyDict::new(py);
|
|
|
kwargs
|
|
|
.set_item(py, "checkambig", check_ambig)
|
|
|
.map_err(|e| vfs_error("dict setitem failed", e))?;
|
|
|
if let Err(e) = self.inner.call_method(
|
|
|
py,
|
|
|
"rename",
|
|
|
(
|
|
|
PyBytes::new(py, &get_bytes_from_path(from)),
|
|
|
PyBytes::new(py, &get_bytes_from_path(to)),
|
|
|
),
|
|
|
Some(&kwargs),
|
|
|
) {
|
|
|
let msg = format!(
|
|
|
"failed to rename '{}' to '{}'",
|
|
|
from.display(),
|
|
|
to.display()
|
|
|
);
|
|
|
return Err(vfs_error(msg, e));
|
|
|
}
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
fn copy(&self, from: &Path, to: &Path) -> Result<(), HgError> {
|
|
|
let gil = &Python::acquire_gil();
|
|
|
let py = gil.python();
|
|
|
let from = self
|
|
|
.inner
|
|
|
.call_method(
|
|
|
py,
|
|
|
"join",
|
|
|
(PyBytes::new(py, &get_bytes_from_path(from)),),
|
|
|
None,
|
|
|
)
|
|
|
.unwrap();
|
|
|
let from = from.extract::<PyBytes>(py).unwrap();
|
|
|
let from = get_path_from_bytes(from.data(py));
|
|
|
let to = self
|
|
|
.inner
|
|
|
.call_method(
|
|
|
py,
|
|
|
"join",
|
|
|
(PyBytes::new(py, &get_bytes_from_path(to)),),
|
|
|
None,
|
|
|
)
|
|
|
.unwrap();
|
|
|
let to = to.extract::<PyBytes>(py).unwrap();
|
|
|
let to = get_path_from_bytes(to.data(py));
|
|
|
std::fs::copy(from, to).when_writing_file(to)?;
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
fn base(&self) -> &Path {
|
|
|
&self.base
|
|
|
}
|
|
|
}
|
|
|
|