use crate::Revnum;
use apr::pool::Pool;
use std::marker::PhantomData;
pub fn version() -> crate::Version {
crate::Version(unsafe { subversion_sys::svn_delta_version() })
}
pub fn default_editor(pool: Pool<'static>) -> WrapEditor<'static> {
let editor = unsafe { subversion_sys::svn_delta_default_editor(pool.as_mut_ptr()) };
let baton = std::ptr::null_mut();
WrapEditor {
editor,
baton,
_pool: apr::PoolHandle::owned(pool),
callback_batons: Vec::new(), }
}
pub fn depth_filter_editor(
wrapped_editor: &WrapEditor,
requested_depth: crate::Depth,
has_target: bool,
pool: Pool<'static>,
) -> WrapEditor<'static> {
let mut editor_ptr: *const subversion_sys::svn_delta_editor_t = std::ptr::null();
let mut edit_baton: *mut std::ffi::c_void = std::ptr::null_mut();
unsafe {
subversion_sys::svn_delta_depth_filter_editor(
&mut editor_ptr,
&mut edit_baton,
wrapped_editor.editor,
wrapped_editor.baton,
requested_depth.into(),
has_target as i32,
pool.as_mut_ptr(),
);
}
WrapEditor {
editor: editor_ptr,
baton: edit_baton,
_pool: apr::PoolHandle::owned(pool),
callback_batons: Vec::new(),
}
}
pub type DropperFn = unsafe fn(*mut std::ffi::c_void);
pub struct WrapEditor<'pool> {
pub(crate) editor: *const subversion_sys::svn_delta_editor_t,
pub(crate) baton: *mut std::ffi::c_void,
pub(crate) _pool: apr::PoolHandle<'pool>,
pub(crate) callback_batons: Vec<(*mut std::ffi::c_void, DropperFn)>,
}
unsafe impl Send for WrapEditor<'_> {}
impl Drop for WrapEditor<'_> {
fn drop(&mut self) {
for (baton, dropper) in &self.callback_batons {
if !baton.is_null() {
unsafe {
dropper(*baton);
}
}
}
self.callback_batons.clear();
}
}
impl<'pool> Editor for WrapEditor<'pool> {
type RootEditor = WrapDirectoryEditor<'pool>;
fn set_target_revision(&mut self, revision: Option<Revnum>) -> Result<(), crate::Error<'_>> {
let scratch_pool = Pool::new();
let err = unsafe {
((*self.editor).set_target_revision.unwrap())(
self.baton,
revision.map_or(-1, |r| r.into()),
scratch_pool.as_mut_ptr(),
)
};
crate::Error::from_raw(err)?;
Ok(())
}
fn open_root(
&mut self,
base_revision: Option<Revnum>,
) -> Result<WrapDirectoryEditor<'pool>, crate::Error<'_>> {
let mut baton = std::ptr::null_mut();
let err = unsafe {
((*self.editor).open_root.unwrap())(
self.baton,
base_revision.map_or(-1, |r| r.into()),
self._pool.as_mut_ptr(),
&mut baton,
)
};
crate::Error::from_raw(err)?;
Ok(WrapDirectoryEditor {
editor: self.editor,
baton,
_pool: unsafe { apr::PoolHandle::from_borrowed_raw(self._pool.as_mut_ptr()) },
})
}
fn close(&mut self) -> Result<(), crate::Error<'_>> {
let scratch_pool = Pool::new();
let err =
unsafe { ((*self.editor).close_edit.unwrap())(self.baton, scratch_pool.as_mut_ptr()) };
crate::Error::from_raw(err)?;
Ok(())
}
fn abort(&mut self) -> Result<(), crate::Error<'_>> {
let scratch_pool = Pool::new();
let err =
unsafe { ((*self.editor).abort_edit.unwrap())(self.baton, scratch_pool.as_mut_ptr()) };
crate::Error::from_raw(err)?;
Ok(())
}
}
impl<'pool> WrapEditor<'pool> {
#[cfg(any(feature = "ra", feature = "repos"))]
pub(crate) fn as_raw_parts(
&self,
) -> (
*const subversion_sys::svn_delta_editor_t,
*mut std::ffi::c_void,
) {
(self.editor, self.baton)
}
}
pub struct WrapDirectoryEditor<'pool> {
pub(crate) editor: *const subversion_sys::svn_delta_editor_t,
pub(crate) baton: *mut std::ffi::c_void,
pub(crate) _pool: apr::PoolHandle<'pool>,
}
impl<'pool> WrapDirectoryEditor<'pool> {
#[cfg(feature = "wc")]
pub(crate) fn as_raw_parts(
&self,
) -> (
*const subversion_sys::svn_delta_editor_t,
*mut std::ffi::c_void,
) {
(self.editor, self.baton)
}
}
impl<'pool> DirectoryEditor for WrapDirectoryEditor<'pool> {
type SubDirectory = WrapDirectoryEditor<'pool>;
type File = WrapFileEditor<'pool>;
fn delete_entry(
&mut self,
path: &str,
revision: Option<Revnum>,
) -> Result<(), crate::Error<'_>> {
let path_cstr = std::ffi::CString::new(path)?;
let scratch_pool = Pool::new();
let err = unsafe {
((*self.editor).delete_entry.unwrap())(
path_cstr.as_ptr(),
revision.map_or(-1, |r| r.into()),
self.baton,
scratch_pool.as_mut_ptr(),
)
};
crate::Error::from_raw(err)?;
Ok(())
}
fn add_directory(
&mut self,
path: &str,
copyfrom: Option<(&str, Revnum)>,
) -> Result<WrapDirectoryEditor<'pool>, crate::Error<'_>> {
let path_cstr = std::ffi::CString::new(path)?;
let copyfrom_path = copyfrom
.map(|(p, _)| std::ffi::CString::new(p))
.transpose()?;
let copyfrom_rev = copyfrom.map(|(_, r)| r.0).unwrap_or(-1);
let mut baton = std::ptr::null_mut();
unsafe {
let err = ((*self.editor).add_directory.unwrap())(
path_cstr.as_ptr(),
self.baton,
if let Some(ref copyfrom_path) = copyfrom_path {
copyfrom_path.as_ptr()
} else {
std::ptr::null()
},
copyfrom_rev,
self._pool.as_mut_ptr(),
&mut baton,
);
crate::Error::from_raw(err)?;
}
Ok(WrapDirectoryEditor {
editor: self.editor,
baton,
_pool: unsafe { apr::PoolHandle::from_borrowed_raw(self._pool.as_mut_ptr()) },
})
}
fn open_directory(
&mut self,
path: &str,
base_revision: Option<Revnum>,
) -> Result<WrapDirectoryEditor<'pool>, crate::Error<'_>> {
let path_cstr = std::ffi::CString::new(path)?;
let mut baton = std::ptr::null_mut();
unsafe {
let err = ((*self.editor).open_directory.unwrap())(
path_cstr.as_ptr(),
self.baton,
base_revision.map_or(-1, |r| r.0),
self._pool.as_mut_ptr(),
&mut baton,
);
crate::Error::from_raw(err)?;
}
Ok(WrapDirectoryEditor {
editor: self.editor,
baton,
_pool: unsafe { apr::PoolHandle::from_borrowed_raw(self._pool.as_mut_ptr()) },
})
}
fn change_prop(&mut self, name: &str, value: Option<&[u8]>) -> Result<(), crate::Error<'_>> {
let scratch_pool = apr::pool::Pool::new();
let name_cstr = std::ffi::CString::new(name)?;
let value_ptr = if let Some(v) = value {
let value = crate::string::BStr::from_bytes(v, &scratch_pool);
value.as_ptr()
} else {
std::ptr::null()
};
let err = unsafe {
((*self.editor).change_dir_prop.unwrap())(
self.baton,
name_cstr.as_ptr(),
value_ptr,
scratch_pool.as_mut_ptr(),
)
};
crate::Error::from_raw(err)?;
Ok(())
}
fn close(&mut self) -> Result<(), crate::Error<'_>> {
let scratch_pool = apr::pool::Pool::new();
let err = unsafe {
((*self.editor).close_directory.unwrap())(self.baton, scratch_pool.as_mut_ptr())
};
crate::Error::from_raw(err)?;
Ok(())
}
fn absent_directory(&mut self, path: &str) -> Result<(), crate::Error<'_>> {
let scratch_pool = apr::pool::Pool::new();
let path_cstr = std::ffi::CString::new(path)?;
let err = unsafe {
((*self.editor).absent_directory.unwrap())(
path_cstr.as_ptr(),
self.baton,
scratch_pool.as_mut_ptr(),
)
};
crate::Error::from_raw(err)?;
Ok(())
}
fn add_file(
&mut self,
path: &str,
copyfrom: Option<(&str, Revnum)>,
) -> Result<WrapFileEditor<'pool>, crate::Error<'_>> {
let path_cstr = std::ffi::CString::new(path)?;
let copyfrom_path = copyfrom
.map(|(p, _)| std::ffi::CString::new(p))
.transpose()?;
let copyfrom_rev = copyfrom.map(|(_, r)| r.0).unwrap_or(-1);
let mut baton = std::ptr::null_mut();
unsafe {
let err = ((*self.editor).add_file.unwrap())(
path_cstr.as_ptr(),
self.baton,
if let Some(ref copyfrom_path) = copyfrom_path {
copyfrom_path.as_ptr()
} else {
std::ptr::null()
},
copyfrom_rev,
self._pool.as_mut_ptr(),
&mut baton,
);
crate::Error::from_raw(err)?;
}
Ok(WrapFileEditor {
editor: self.editor,
baton,
_pool: unsafe { apr::PoolHandle::from_borrowed_raw(self._pool.as_mut_ptr()) },
})
}
fn open_file(
&mut self,
path: &str,
base_revision: Option<Revnum>,
) -> Result<WrapFileEditor<'pool>, crate::Error<'_>> {
let path_cstr = std::ffi::CString::new(path)?;
let mut baton = std::ptr::null_mut();
unsafe {
let err = ((*self.editor).open_file.unwrap())(
path_cstr.as_ptr(),
self.baton,
base_revision.map_or(-1, |r| r.into()),
self._pool.as_mut_ptr(),
&mut baton,
);
crate::Error::from_raw(err)?;
}
Ok(WrapFileEditor {
editor: self.editor,
baton,
_pool: unsafe { apr::PoolHandle::from_borrowed_raw(self._pool.as_mut_ptr()) },
})
}
fn absent_file(&mut self, path: &str) -> Result<(), crate::Error<'_>> {
let scratch_pool = apr::pool::Pool::new();
let path_cstr = std::ffi::CString::new(path)?;
let err = unsafe {
((*self.editor).absent_file.unwrap())(
path_cstr.as_ptr(),
self.baton,
scratch_pool.as_mut_ptr(),
)
};
crate::Error::from_raw(err)?;
Ok(())
}
}
pub struct WrapFileEditor<'pool> {
editor: *const subversion_sys::svn_delta_editor_t,
baton: *mut std::ffi::c_void,
_pool: apr::PoolHandle<'pool>,
}
impl<'pool> WrapFileEditor<'pool> {
#[cfg(feature = "wc")]
pub(crate) fn as_raw_parts(
&self,
) -> (
*const subversion_sys::svn_delta_editor_t,
*mut std::ffi::c_void,
) {
(self.editor, self.baton)
}
pub fn apply_textdelta_raw(
&mut self,
base_checksum: Option<&str>,
) -> Result<WrapTxdeltaWindowHandler, crate::Error<'static>> {
let pool = apr::pool::Pool::new();
let base_checksum_cstr = base_checksum.map(std::ffi::CString::new).transpose()?;
let mut handler: subversion_sys::svn_txdelta_window_handler_t = None;
let mut baton = std::ptr::null_mut();
let err = unsafe {
((*self.editor).apply_textdelta.unwrap())(
self.baton,
if let Some(ref base_checksum_cstr) = base_checksum_cstr {
base_checksum_cstr.as_ptr()
} else {
std::ptr::null()
},
pool.as_mut_ptr(),
&mut handler,
&mut baton,
)
};
crate::Error::from_raw(err)?;
Ok(WrapTxdeltaWindowHandler::from_raw(handler, baton, pool))
}
}
pub struct WrapTxdeltaWindowHandler {
handler: subversion_sys::svn_txdelta_window_handler_t,
baton: *mut std::ffi::c_void,
_pool: apr::Pool<'static>,
_phantom: PhantomData<*mut ()>,
}
impl Drop for WrapTxdeltaWindowHandler {
fn drop(&mut self) {
}
}
impl WrapTxdeltaWindowHandler {
pub(crate) fn from_raw(
handler: subversion_sys::svn_txdelta_window_handler_t,
baton: *mut std::ffi::c_void,
pool: apr::Pool<'static>,
) -> Self {
Self {
handler,
baton,
_pool: pool,
_phantom: PhantomData,
}
}
pub fn call(&self, window: &mut TxDeltaWindow) -> Result<(), crate::Error<'static>> {
let err = unsafe { (self.handler.unwrap())(window.as_mut_ptr(), self.baton) };
crate::Error::from_raw(err)?;
Ok(())
}
pub fn finish(&self) -> Result<(), crate::Error<'static>> {
let err = unsafe { (self.handler.unwrap())(std::ptr::null_mut(), self.baton) };
crate::Error::from_raw(err)?;
Ok(())
}
}
impl<'pool> FileEditor for WrapFileEditor<'pool> {
fn apply_textdelta(
&mut self,
base_checksum: Option<&str>,
) -> Result<
Box<dyn for<'b> Fn(&'b mut TxDeltaWindow) -> Result<(), crate::Error<'static>>>,
crate::Error<'static>,
> {
let pool = apr::pool::Pool::new();
let base_checksum_cstr = base_checksum.map(std::ffi::CString::new).transpose()?;
let mut handler = None;
let mut baton = std::ptr::null_mut();
let err = unsafe {
((*self.editor).apply_textdelta.unwrap())(
self.baton,
if let Some(ref base_checksum_cstr) = base_checksum_cstr {
base_checksum_cstr.as_ptr()
} else {
std::ptr::null()
},
pool.as_mut_ptr(),
&mut handler,
&mut baton,
)
};
crate::Error::from_raw(err)?;
let apply = move |window: &mut TxDeltaWindow| -> Result<(), crate::Error<'static>> {
let err = unsafe { (handler.unwrap())(window.as_mut_ptr(), baton) };
crate::Error::from_raw(err)?;
Ok(())
};
Ok(Box::new(apply))
}
fn change_prop(
&mut self,
name: &str,
value: Option<&[u8]>,
) -> Result<(), crate::Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let name_cstr = std::ffi::CString::new(name)?;
let value_ptr = if let Some(v) = value {
let value = crate::string::BStr::from_bytes(v, &scratch_pool);
value.as_ptr()
} else {
std::ptr::null()
};
let err = unsafe {
((*self.editor).change_file_prop.unwrap())(
self.baton,
name_cstr.as_ptr(),
value_ptr,
scratch_pool.as_mut_ptr(),
)
};
crate::Error::from_raw(err)?;
Ok(())
}
fn close(&mut self, text_checksum: Option<&str>) -> Result<(), crate::Error<'static>> {
let pool = apr::pool::Pool::new();
let text_checksum_cstr = text_checksum.map(std::ffi::CString::new).transpose()?;
let err = unsafe {
((*self.editor).close_file.unwrap())(
self.baton,
if let Some(ref text_checksum_cstr) = text_checksum_cstr {
text_checksum_cstr.as_ptr()
} else {
std::ptr::null()
},
pool.as_mut_ptr(),
)
};
crate::Error::from_raw(err)?;
Ok(())
}
}
pub trait Editor {
type RootEditor: DirectoryEditor;
fn set_target_revision(&mut self, revision: Option<Revnum>) -> Result<(), crate::Error<'_>>;
fn open_root(
&mut self,
base_revision: Option<Revnum>,
) -> Result<Self::RootEditor, crate::Error<'_>>;
fn close(&mut self) -> Result<(), crate::Error<'_>>;
fn abort(&mut self) -> Result<(), crate::Error<'_>>;
}
pub trait DirectoryEditor {
type SubDirectory: DirectoryEditor;
type File: FileEditor;
fn delete_entry(
&mut self,
path: &str,
revision: Option<Revnum>,
) -> Result<(), crate::Error<'_>>;
fn add_directory(
&mut self,
path: &str,
copyfrom: Option<(&str, Revnum)>,
) -> Result<Self::SubDirectory, crate::Error<'_>>;
fn open_directory(
&mut self,
path: &str,
base_revision: Option<Revnum>,
) -> Result<Self::SubDirectory, crate::Error<'_>>;
fn change_prop(&mut self, name: &str, value: Option<&[u8]>) -> Result<(), crate::Error<'_>>;
fn close(&mut self) -> Result<(), crate::Error<'_>>;
fn absent_directory(&mut self, path: &str) -> Result<(), crate::Error<'_>>;
fn add_file(
&mut self,
path: &str,
copyfrom: Option<(&str, Revnum)>,
) -> Result<Self::File, crate::Error<'_>>;
fn open_file(
&mut self,
path: &str,
base_revision: Option<Revnum>,
) -> Result<Self::File, crate::Error<'_>>;
fn absent_file(&mut self, path: &str) -> Result<(), crate::Error<'_>>;
}
pub fn noop_window_handler(window: &mut TxDeltaWindow) -> Result<(), crate::Error<'_>> {
let err = unsafe {
subversion_sys::svn_delta_noop_window_handler(window.as_mut_ptr(), std::ptr::null_mut())
};
crate::Error::from_raw(err)?;
Ok(())
}
pub trait FileEditor {
fn apply_textdelta(
&mut self,
base_checksum: Option<&str>,
) -> Result<
Box<dyn for<'a> Fn(&'a mut TxDeltaWindow) -> Result<(), crate::Error<'static>>>,
crate::Error<'static>,
>;
fn apply_textdelta_stream(
&mut self,
_base_checksum: Option<&str>,
_open_stream: Box<dyn FnOnce() -> Result<TxDeltaStream, crate::Error<'static>>>,
) -> Result<(), crate::Error<'static>> {
Err(crate::Error::from_message(
"apply_textdelta_stream not supported by this editor",
))
}
fn change_prop(
&mut self,
name: &str,
value: Option<&[u8]>,
) -> Result<(), crate::Error<'static>>;
fn close(&mut self, text_checksum: Option<&str>) -> Result<(), crate::Error<'static>>;
}
pub struct TxDeltaWindow {
ptr: *mut subversion_sys::svn_txdelta_window_t,
pool: apr::Pool<'static>,
_phantom: PhantomData<*mut ()>, }
impl Drop for TxDeltaWindow {
fn drop(&mut self) {
}
}
impl Default for TxDeltaWindow {
fn default() -> Self {
Self::new()
}
}
impl TxDeltaWindow {
pub fn pool(&self) -> &apr::Pool<'_> {
&self.pool
}
pub fn as_mut_ptr(&mut self) -> *mut subversion_sys::svn_txdelta_window_t {
self.ptr
}
pub fn as_ptr(&self) -> *const subversion_sys::svn_txdelta_window_t {
self.ptr
}
pub fn new() -> Self {
let pool = apr::Pool::new();
let ptr = pool.calloc::<subversion_sys::svn_txdelta_window_t>();
Self {
ptr,
pool,
_phantom: PhantomData,
}
}
pub fn from_parts(
sview_offset: u64,
sview_len: u64,
tview_len: u64,
src_ops: i32,
ops: &[(i32, u64, u64)],
new_data: &[u8],
) -> Self {
let pool = apr::Pool::new();
unsafe {
let window_ptr = pool.calloc::<subversion_sys::svn_txdelta_window_t>();
let ops_ptr = if ops.is_empty() {
std::ptr::null_mut()
} else {
apr_sys::apr_palloc(
pool.as_mut_ptr(),
std::mem::size_of::<subversion_sys::svn_txdelta_op_t>() * ops.len(),
) as *mut subversion_sys::svn_txdelta_op_t
};
for (i, &(action_code, offset, length)) in ops.iter().enumerate() {
(*ops_ptr.add(i)).action_code = action_code as _;
(*ops_ptr.add(i)).offset = offset as apr_sys::apr_size_t;
(*ops_ptr.add(i)).length = length as apr_sys::apr_size_t;
}
let new_data_ptr = if new_data.is_empty() {
std::ptr::null()
} else {
let svn_string = apr_sys::apr_palloc(
pool.as_mut_ptr(),
std::mem::size_of::<subversion_sys::svn_string_t>(),
) as *mut subversion_sys::svn_string_t;
let data_buf = apr_sys::apr_palloc(pool.as_mut_ptr(), new_data.len())
as *mut std::os::raw::c_char;
std::ptr::copy_nonoverlapping(
new_data.as_ptr(),
data_buf as *mut u8,
new_data.len(),
);
(*svn_string).data = data_buf;
(*svn_string).len = new_data.len();
svn_string as *const subversion_sys::svn_string_t
};
(*window_ptr).sview_offset = sview_offset as subversion_sys::svn_filesize_t;
(*window_ptr).sview_len = sview_len as apr_sys::apr_size_t;
(*window_ptr).tview_len = tview_len as apr_sys::apr_size_t;
(*window_ptr).num_ops = ops.len() as std::os::raw::c_int;
(*window_ptr).src_ops = src_ops as std::os::raw::c_int;
(*window_ptr).ops = ops_ptr;
(*window_ptr).new_data = new_data_ptr;
Self {
ptr: window_ptr,
pool,
_phantom: PhantomData,
}
}
}
pub fn sview_len(&self) -> apr_sys::apr_size_t {
unsafe { (*self.ptr).sview_len }
}
pub fn tview_len(&self) -> apr_sys::apr_size_t {
unsafe { (*self.ptr).tview_len }
}
pub fn sview_offset(&self) -> crate::FileSize {
unsafe { (*self.ptr).sview_offset }
}
pub fn src_ops(&self) -> i32 {
unsafe { (*self.ptr).src_ops as i32 }
}
pub fn ops(&self) -> Vec<(i32, u64, u64)> {
unsafe {
let num = (*self.ptr).num_ops as usize;
let ops_ptr = (*self.ptr).ops;
if ops_ptr.is_null() || num == 0 {
return Vec::new();
}
(0..num)
.map(|i| {
let op = &*ops_ptr.add(i);
(op.action_code as i32, op.offset as u64, op.length as u64)
})
.collect()
}
}
pub fn new_data(&self) -> &[u8] {
unsafe {
let nd = (*self.ptr).new_data;
if nd.is_null() || (*nd).data.is_null() {
&[]
} else {
std::slice::from_raw_parts((*nd).data as *const u8, (*nd).len)
}
}
}
pub fn compose(a: &Self, b: &Self) -> Self {
let pool = apr::Pool::new();
let ptr =
unsafe { subversion_sys::svn_txdelta_compose_windows(a.ptr, b.ptr, pool.as_mut_ptr()) };
Self {
ptr,
pool,
_phantom: PhantomData,
}
}
pub fn apply_instructions(
&mut self,
source: &mut [u8],
target: &mut Vec<u8>,
) -> Result<(), crate::Error<'_>> {
unsafe {
target.resize(self.tview_len(), 0);
let mut tlen = target.len() as apr_sys::apr_size_t;
subversion_sys::svn_txdelta_apply_instructions(
self.ptr,
source.as_ptr() as *mut i8,
target.as_mut_ptr() as *mut i8,
&mut tlen,
);
}
Ok(())
}
pub fn dup(&self) -> Self {
let pool = apr::Pool::new();
let ptr = unsafe { subversion_sys::svn_txdelta_window_dup(self.ptr, pool.as_mut_ptr()) };
Self {
ptr,
pool,
_phantom: PhantomData,
}
}
}
unsafe extern "C" fn rust_editor_set_target_revision<E: Editor>(
edit_baton: *mut std::ffi::c_void,
target_revision: subversion_sys::svn_revnum_t,
_scratch_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let editor = &mut *(edit_baton as *mut E);
let revision = if target_revision < 0 {
None
} else {
Some(Revnum(target_revision))
};
match editor.set_target_revision(revision) {
Ok(()) => std::ptr::null_mut(),
Err(e) => e.into_raw(),
}
}
unsafe extern "C" fn rust_editor_open_root<E, D>(
edit_baton: *mut std::ffi::c_void,
base_revision: subversion_sys::svn_revnum_t,
_dir_pool: *mut apr_sys::apr_pool_t,
root_baton: *mut *mut std::ffi::c_void,
) -> *mut subversion_sys::svn_error_t
where
E: Editor<RootEditor = D>,
D: DirectoryEditor,
{
let editor = &mut *(edit_baton as *mut E);
let revision = if base_revision < 0 {
None
} else {
Some(Revnum(base_revision))
};
match editor.open_root(revision) {
Ok(dir_editor) => {
*root_baton = Box::into_raw(Box::new(dir_editor)) as *mut std::ffi::c_void;
std::ptr::null_mut()
}
Err(e) => e.into_raw(),
}
}
unsafe extern "C" fn rust_editor_close<E: Editor>(
edit_baton: *mut std::ffi::c_void,
_scratch_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let editor = &mut *(edit_baton as *mut E);
match editor.close() {
Ok(()) => std::ptr::null_mut(),
Err(e) => e.into_raw(),
}
}
unsafe extern "C" fn rust_editor_abort<E: Editor>(
edit_baton: *mut std::ffi::c_void,
_scratch_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let editor = &mut *(edit_baton as *mut E);
match editor.abort() {
Ok(()) => std::ptr::null_mut(),
Err(e) => e.into_raw(),
}
}
unsafe extern "C" fn rust_dir_delete_entry<D: DirectoryEditor>(
path: *const std::ffi::c_char,
revision: subversion_sys::svn_revnum_t,
parent_baton: *mut std::ffi::c_void,
_scratch_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let dir = &mut *(parent_baton as *mut D);
let path_str = std::ffi::CStr::from_ptr(path).to_str().unwrap();
let rev = if revision < 0 {
None
} else {
Some(Revnum(revision))
};
match dir.delete_entry(path_str, rev) {
Ok(()) => std::ptr::null_mut(),
Err(e) => e.into_raw(),
}
}
unsafe extern "C" fn rust_dir_add_directory<D>(
path: *const std::ffi::c_char,
parent_baton: *mut std::ffi::c_void,
copyfrom_path: *const std::ffi::c_char,
copyfrom_revision: subversion_sys::svn_revnum_t,
_child_pool: *mut apr_sys::apr_pool_t,
child_baton: *mut *mut std::ffi::c_void,
) -> *mut subversion_sys::svn_error_t
where
D: DirectoryEditor<SubDirectory = D>,
{
let dir = &mut *(parent_baton as *mut D);
let path_str = std::ffi::CStr::from_ptr(path).to_str().unwrap();
let copyfrom = if copyfrom_path.is_null() {
None
} else {
let cf_path = std::ffi::CStr::from_ptr(copyfrom_path).to_str().unwrap();
Some((cf_path, Revnum(copyfrom_revision)))
};
match dir.add_directory(path_str, copyfrom) {
Ok(subdir) => {
*child_baton = Box::into_raw(Box::new(subdir)) as *mut std::ffi::c_void;
std::ptr::null_mut()
}
Err(e) => e.into_raw(),
}
}
unsafe extern "C" fn rust_dir_open_directory<D>(
path: *const std::ffi::c_char,
parent_baton: *mut std::ffi::c_void,
base_revision: subversion_sys::svn_revnum_t,
_child_pool: *mut apr_sys::apr_pool_t,
child_baton: *mut *mut std::ffi::c_void,
) -> *mut subversion_sys::svn_error_t
where
D: DirectoryEditor<SubDirectory = D>,
{
let dir = &mut *(parent_baton as *mut D);
let path_str = std::ffi::CStr::from_ptr(path).to_str().unwrap();
let revision = if base_revision < 0 {
None
} else {
Some(Revnum(base_revision))
};
match dir.open_directory(path_str, revision) {
Ok(subdir) => {
*child_baton = Box::into_raw(Box::new(subdir)) as *mut std::ffi::c_void;
std::ptr::null_mut()
}
Err(e) => e.into_raw(),
}
}
unsafe extern "C" fn rust_dir_close<D: DirectoryEditor>(
dir_baton: *mut std::ffi::c_void,
_scratch_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let dir = &mut *(dir_baton as *mut D);
match dir.close() {
Ok(()) => {
let _ = Box::from_raw(dir_baton as *mut D);
std::ptr::null_mut()
}
Err(e) => e.into_raw(),
}
}
unsafe extern "C" fn rust_dir_absent_directory<D: DirectoryEditor>(
path: *const std::ffi::c_char,
parent_baton: *mut std::ffi::c_void,
_scratch_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let dir = &mut *(parent_baton as *mut D);
let path_str = std::ffi::CStr::from_ptr(path).to_str().unwrap();
match dir.absent_directory(path_str) {
Ok(()) => std::ptr::null_mut(),
Err(e) => e.into_raw(),
}
}
unsafe extern "C" fn rust_dir_change_prop<D: DirectoryEditor>(
dir_baton: *mut std::ffi::c_void,
name: *const std::ffi::c_char,
value: *const subversion_sys::svn_string_t,
_scratch_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let dir = &mut *(dir_baton as *mut D);
let name_str = std::ffi::CStr::from_ptr(name).to_str().unwrap();
let value_bytes = if value.is_null() {
None
} else {
Some(std::slice::from_raw_parts(
(*value).data as *const u8,
(*value).len,
))
};
match dir.change_prop(name_str, value_bytes) {
Ok(()) => std::ptr::null_mut(),
Err(e) => e.into_raw(),
}
}
unsafe extern "C" fn rust_dir_add_file<D, F>(
path: *const std::ffi::c_char,
parent_baton: *mut std::ffi::c_void,
copyfrom_path: *const std::ffi::c_char,
copyfrom_revision: subversion_sys::svn_revnum_t,
_file_pool: *mut apr_sys::apr_pool_t,
file_baton: *mut *mut std::ffi::c_void,
) -> *mut subversion_sys::svn_error_t
where
D: DirectoryEditor<File = F>,
F: FileEditor,
{
let dir = &mut *(parent_baton as *mut D);
let path_str = std::ffi::CStr::from_ptr(path).to_str().unwrap();
let copyfrom = if copyfrom_path.is_null() {
None
} else {
let cf_path = std::ffi::CStr::from_ptr(copyfrom_path).to_str().unwrap();
Some((cf_path, Revnum(copyfrom_revision)))
};
match dir.add_file(path_str, copyfrom) {
Ok(file) => {
*file_baton = Box::into_raw(Box::new(file)) as *mut std::ffi::c_void;
std::ptr::null_mut()
}
Err(e) => e.into_raw(),
}
}
unsafe extern "C" fn rust_dir_open_file<D, F>(
path: *const std::ffi::c_char,
parent_baton: *mut std::ffi::c_void,
base_revision: subversion_sys::svn_revnum_t,
_file_pool: *mut apr_sys::apr_pool_t,
file_baton: *mut *mut std::ffi::c_void,
) -> *mut subversion_sys::svn_error_t
where
D: DirectoryEditor<File = F>,
F: FileEditor,
{
let dir = &mut *(parent_baton as *mut D);
let path_str = std::ffi::CStr::from_ptr(path).to_str().unwrap();
let revision = if base_revision < 0 {
None
} else {
Some(Revnum(base_revision))
};
match dir.open_file(path_str, revision) {
Ok(file) => {
*file_baton = Box::into_raw(Box::new(file)) as *mut std::ffi::c_void;
std::ptr::null_mut()
}
Err(e) => e.into_raw(),
}
}
unsafe extern "C" fn rust_dir_absent_file<D: DirectoryEditor>(
path: *const std::ffi::c_char,
parent_baton: *mut std::ffi::c_void,
_scratch_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let dir = &mut *(parent_baton as *mut D);
let path_str = std::ffi::CStr::from_ptr(path).to_str().unwrap();
match dir.absent_file(path_str) {
Ok(()) => std::ptr::null_mut(),
Err(e) => e.into_raw(),
}
}
unsafe extern "C" fn rust_file_change_prop<F: FileEditor>(
file_baton: *mut std::ffi::c_void,
name: *const std::ffi::c_char,
value: *const subversion_sys::svn_string_t,
_scratch_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let file = &mut *(file_baton as *mut F);
let name_str = std::ffi::CStr::from_ptr(name).to_str().unwrap();
let value_bytes = if value.is_null() {
None
} else {
Some(std::slice::from_raw_parts(
(*value).data as *const u8,
(*value).len,
))
};
match file.change_prop(name_str, value_bytes) {
Ok(()) => std::ptr::null_mut(),
Err(e) => e.into_raw(),
}
}
unsafe extern "C" fn rust_file_close<F: FileEditor>(
file_baton: *mut std::ffi::c_void,
text_checksum: *const std::ffi::c_char,
_scratch_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let file = &mut *(file_baton as *mut F);
let checksum = if text_checksum.is_null() {
None
} else {
Some(std::ffi::CStr::from_ptr(text_checksum).to_str().unwrap())
};
match file.close(checksum) {
Ok(()) => {
let _ = Box::from_raw(file_baton as *mut F);
std::ptr::null_mut()
}
Err(e) => e.into_raw(),
}
}
unsafe extern "C" fn rust_txdelta_window_handler(
window: *mut subversion_sys::svn_txdelta_window_t,
baton: *mut std::ffi::c_void,
) -> *mut subversion_sys::svn_error_t {
let closure = &*(baton
as *const Box<dyn for<'a> Fn(&'a mut TxDeltaWindow) -> Result<(), crate::Error<'static>>>);
if window.is_null() {
let mut w = TxDeltaWindow {
ptr: std::ptr::null_mut(),
pool: apr::Pool::new(),
_phantom: PhantomData,
};
let result = match closure(&mut w) {
Ok(()) => std::ptr::null_mut(),
Err(e) => e.into_raw(),
};
let _ = Box::from_raw(
baton
as *mut Box<
dyn for<'a> Fn(&'a mut TxDeltaWindow) -> Result<(), crate::Error<'static>>,
>,
);
result
} else {
let mut w = TxDeltaWindow {
ptr: window,
pool: apr::Pool::new(),
_phantom: PhantomData,
};
match closure(&mut w) {
Ok(()) => std::ptr::null_mut(),
Err(e) => e.into_raw(),
}
}
}
unsafe extern "C" fn rust_file_apply_textdelta<F: FileEditor>(
file_baton: *mut std::ffi::c_void,
base_checksum: *const std::ffi::c_char,
_result_pool: *mut apr_sys::apr_pool_t,
handler: *mut subversion_sys::svn_txdelta_window_handler_t,
handler_baton: *mut *mut std::ffi::c_void,
) -> *mut subversion_sys::svn_error_t {
let file = &mut *(file_baton as *mut F);
let checksum = if base_checksum.is_null() {
None
} else {
std::ffi::CStr::from_ptr(base_checksum).to_str().ok()
};
match file.apply_textdelta(checksum) {
Ok(closure) => {
let boxed: Box<
Box<dyn for<'a> Fn(&'a mut TxDeltaWindow) -> Result<(), crate::Error<'static>>>,
> = Box::new(closure);
*handler_baton = Box::into_raw(boxed) as *mut std::ffi::c_void;
*handler = Some(rust_txdelta_window_handler);
std::ptr::null_mut()
}
Err(e) => e.into_raw(),
}
}
fn rust_editor_vtable<E, D, F>() -> subversion_sys::svn_delta_editor_t
where
E: Editor<RootEditor = D> + 'static,
D: DirectoryEditor<SubDirectory = D, File = F> + 'static,
F: FileEditor + 'static,
{
subversion_sys::svn_delta_editor_t {
set_target_revision: Some(rust_editor_set_target_revision::<E>),
open_root: Some(rust_editor_open_root::<E, D>),
delete_entry: Some(rust_dir_delete_entry::<D>),
add_directory: Some(rust_dir_add_directory::<D>),
open_directory: Some(rust_dir_open_directory::<D>),
change_dir_prop: Some(rust_dir_change_prop::<D>),
close_directory: Some(rust_dir_close::<D>),
absent_directory: Some(rust_dir_absent_directory::<D>),
add_file: Some(rust_dir_add_file::<D, F>),
open_file: Some(rust_dir_open_file::<D, F>),
apply_textdelta: Some(rust_file_apply_textdelta::<F>),
change_file_prop: Some(rust_file_change_prop::<F>),
close_file: Some(rust_file_close::<F>),
absent_file: Some(rust_dir_absent_file::<D>),
close_edit: Some(rust_editor_close::<E>),
abort_edit: Some(rust_editor_abort::<E>),
apply_textdelta_stream: None,
}
}
impl WrapEditor<'static> {
pub fn from_rust_editor<E, D, F>(editor: E) -> Self
where
E: Editor<RootEditor = D> + 'static,
D: DirectoryEditor<SubDirectory = D, File = F> + 'static,
F: FileEditor + 'static,
{
let pool = Pool::new();
let editor_box = Box::new(editor);
let editor_ptr = Box::into_raw(editor_box) as *mut std::ffi::c_void;
let dropper: DropperFn = |ptr| unsafe {
let _ = Box::from_raw(ptr as *mut E);
};
let vtable = rust_editor_vtable::<E, D, F>();
let vtable_ptr = unsafe {
let ptr = apr_sys::apr_palloc(
pool.as_mut_ptr(),
std::mem::size_of::<subversion_sys::svn_delta_editor_t>(),
) as *mut subversion_sys::svn_delta_editor_t;
*ptr = vtable;
ptr as *const subversion_sys::svn_delta_editor_t
};
WrapEditor {
editor: vtable_ptr,
baton: editor_ptr,
_pool: apr::PoolHandle::owned(pool),
callback_batons: vec![(editor_ptr, dropper)],
}
}
}
pub struct TxDeltaStream {
ptr: *mut subversion_sys::svn_txdelta_stream_t,
_pool: apr::Pool<'static>,
}
impl TxDeltaStream {
pub fn new(source: &mut crate::io::Stream, target: &mut crate::io::Stream) -> Self {
let pool = apr::Pool::new();
let mut stream_ptr: *mut subversion_sys::svn_txdelta_stream_t = std::ptr::null_mut();
unsafe {
subversion_sys::svn_txdelta(
&mut stream_ptr,
source.as_mut_ptr(),
target.as_mut_ptr(),
pool.as_mut_ptr(),
);
}
Self {
ptr: stream_ptr,
_pool: pool,
}
}
pub(crate) fn from_raw(
ptr: *mut subversion_sys::svn_txdelta_stream_t,
pool: apr::Pool<'static>,
) -> Self {
Self { ptr, _pool: pool }
}
pub fn next_window(&mut self) -> Result<Option<TxDeltaWindow>, crate::Error<'_>> {
let mut window_ptr: *mut subversion_sys::svn_txdelta_window_t = std::ptr::null_mut();
let ret = unsafe {
subversion_sys::svn_txdelta_next_window(
&mut window_ptr,
self.ptr,
self._pool.as_mut_ptr(),
)
};
crate::svn_result(ret)?;
if window_ptr.is_null() {
Ok(None)
} else {
let mut window = TxDeltaWindow::new();
window.ptr = window_ptr;
Ok(Some(window))
}
}
pub fn md5_digest(&self) -> Vec<u8> {
let digest_ptr = unsafe { subversion_sys::svn_txdelta_md5_digest(self.ptr) };
if digest_ptr.is_null() {
Vec::new()
} else {
let mut digest = Vec::with_capacity(16);
unsafe {
for i in 0..16 {
digest.push(*digest_ptr.add(i));
}
}
digest
}
}
}
pub fn txdelta(source: &mut crate::io::Stream, target: &mut crate::io::Stream) -> TxDeltaStream {
let pool = apr::Pool::new();
let mut stream_ptr: *mut subversion_sys::svn_txdelta_stream_t = std::ptr::null_mut();
unsafe {
subversion_sys::svn_txdelta(
&mut stream_ptr,
source.as_mut_ptr(),
target.as_mut_ptr(),
pool.as_mut_ptr(),
);
}
TxDeltaStream {
ptr: stream_ptr,
_pool: pool,
}
}
pub fn txdelta2(
source: &mut crate::io::Stream,
target: &mut crate::io::Stream,
calculate_checksum: bool,
) -> TxDeltaStream {
let pool = apr::Pool::new();
let mut stream_ptr: *mut subversion_sys::svn_txdelta_stream_t = std::ptr::null_mut();
unsafe {
subversion_sys::svn_txdelta2(
&mut stream_ptr,
source.as_mut_ptr(),
target.as_mut_ptr(),
calculate_checksum as i32,
pool.as_mut_ptr(),
);
}
TxDeltaStream {
ptr: stream_ptr,
_pool: pool,
}
}
pub unsafe fn send_string(
string: &str,
handler: subversion_sys::svn_txdelta_window_handler_t,
handler_baton: *mut std::ffi::c_void,
) -> Result<(), crate::Error<'_>> {
let pool = apr::Pool::new();
let svn_string = unsafe {
subversion_sys::svn_string_ncreate(
string.as_ptr() as *const i8,
string.len(),
pool.as_mut_ptr(),
)
};
let ret = unsafe {
subversion_sys::svn_txdelta_send_string(
svn_string,
handler,
handler_baton,
pool.as_mut_ptr(),
)
};
crate::svn_result(ret)
}
pub unsafe fn send_stream(
stream: &mut crate::io::Stream,
handler: subversion_sys::svn_txdelta_window_handler_t,
handler_baton: *mut std::ffi::c_void,
digest: Option<&mut [u8; 16]>,
) -> Result<(), crate::Error<'static>> {
let pool = apr::Pool::new();
let digest_ptr = digest.map_or(std::ptr::null_mut(), |d| d.as_mut_ptr());
let ret = unsafe {
subversion_sys::svn_txdelta_send_stream(
stream.as_mut_ptr(),
handler,
handler_baton,
digest_ptr,
pool.as_mut_ptr(),
)
};
crate::svn_result(ret)
}
pub unsafe fn send_txstream(
txstream: &mut TxDeltaStream,
handler: subversion_sys::svn_txdelta_window_handler_t,
handler_baton: *mut std::ffi::c_void,
) -> Result<(), crate::Error<'_>> {
let pool = apr::Pool::new();
let ret = unsafe {
subversion_sys::svn_txdelta_send_txstream(
txstream.ptr,
handler,
handler_baton,
pool.as_mut_ptr(),
)
};
crate::svn_result(ret)
}
pub unsafe fn send_contents(
contents: &[u8],
handler: subversion_sys::svn_txdelta_window_handler_t,
handler_baton: *mut std::ffi::c_void,
) -> Result<(), crate::Error<'static>> {
let pool = apr::Pool::new();
let ret = unsafe {
subversion_sys::svn_txdelta_send_contents(
contents.as_ptr(),
contents.len(),
handler,
handler_baton,
pool.as_mut_ptr(),
)
};
crate::svn_result(ret)
}
pub unsafe fn target_push(
handler: subversion_sys::svn_txdelta_window_handler_t,
handler_baton: *mut std::ffi::c_void,
source: &mut crate::io::Stream,
) -> crate::io::Stream {
let pool = apr::Pool::new();
let stream_ptr = unsafe {
subversion_sys::svn_txdelta_target_push(
handler,
handler_baton,
source.as_mut_ptr(),
pool.as_mut_ptr(),
)
};
crate::io::Stream::from_ptr(stream_ptr, pool)
}
pub fn to_svndiff_stream(
txstream: &mut TxDeltaStream,
svndiff_version: i32,
compression_level: i32,
) -> crate::io::Stream {
let pool = apr::Pool::new();
let stream_ptr = unsafe {
subversion_sys::svn_txdelta_to_svndiff_stream(
txstream.ptr,
svndiff_version,
compression_level,
pool.as_mut_ptr(),
)
};
crate::io::Stream::from_ptr(stream_ptr, pool)
}
pub fn read_svndiff_window(
stream: &mut crate::io::Stream,
svndiff_version: i32,
) -> Result<Option<TxDeltaWindow>, crate::Error<'static>> {
let pool = apr::Pool::new();
let mut window_ptr: *mut subversion_sys::svn_txdelta_window_t = std::ptr::null_mut();
let ret = unsafe {
subversion_sys::svn_txdelta_read_svndiff_window(
&mut window_ptr,
stream.as_mut_ptr(),
svndiff_version,
pool.as_mut_ptr(),
)
};
crate::svn_result(ret)?;
if window_ptr.is_null() {
Ok(None)
} else {
let mut window = TxDeltaWindow::new();
window.ptr = window_ptr;
Ok(Some(window))
}
}
pub fn apply(
source: &mut crate::io::Stream,
target: &mut crate::io::Stream,
) -> Result<
(
subversion_sys::svn_txdelta_window_handler_t,
*mut std::ffi::c_void,
),
crate::Error<'static>,
> {
let pool = apr::Pool::new();
let mut handler: subversion_sys::svn_txdelta_window_handler_t = None;
let mut handler_baton: *mut std::ffi::c_void = std::ptr::null_mut();
unsafe {
subversion_sys::svn_txdelta_apply(
source.as_mut_ptr(),
target.as_mut_ptr(),
std::ptr::null_mut(), std::ptr::null_mut(), pool.as_mut_ptr(),
&mut handler,
&mut handler_baton,
);
}
Ok((handler, handler_baton))
}
pub fn parse_svndiff<F>(handler: &mut F) -> Result<crate::io::Stream, crate::Error<'_>>
where
F: FnMut(&mut TxDeltaWindow) -> Result<(), crate::Error>,
{
let pool = apr::Pool::new();
let handler_ptr = handler as *mut F as *mut std::ffi::c_void;
extern "C" fn window_handler_wrapper<F>(
window: *mut subversion_sys::svn_txdelta_window_t,
baton: *mut std::ffi::c_void,
) -> *mut subversion_sys::svn_error_t
where
F: FnMut(&mut TxDeltaWindow) -> Result<(), crate::Error>,
{
unsafe {
let handler = &mut *(baton as *mut F);
if window.is_null() {
return std::ptr::null_mut();
}
let mut tx_window = TxDeltaWindow::new();
tx_window.ptr = window;
let result = handler(&mut tx_window);
match result {
Ok(()) => std::ptr::null_mut(),
Err(err) => err.as_ptr() as *mut subversion_sys::svn_error_t,
}
}
}
let stream_ptr = unsafe {
subversion_sys::svn_txdelta_parse_svndiff(
Some(window_handler_wrapper::<F>),
handler_ptr,
1, pool.as_mut_ptr(),
)
};
if stream_ptr.is_null() {
return Err(crate::Error::from_raw(unsafe {
subversion_sys::svn_error_create(
subversion_sys::svn_errno_t_SVN_ERR_DELTA_MD5_CHECKSUM_ABSENT as i32,
std::ptr::null_mut(),
c"Failed to create svndiff parser".as_ptr(),
)
})
.unwrap_err());
}
Ok(crate::io::Stream::from_ptr(stream_ptr, pool))
}
pub fn svndiff_from_streams(
source: Option<&mut crate::io::Stream>,
target: &mut crate::io::Stream,
output: &mut crate::io::Stream,
svndiff_version: i32,
compression_level: i32,
checksum_kind: crate::ChecksumKind,
cancel_func: Option<Box<dyn Fn() -> Result<(), crate::Error<'static>>>>,
) -> Result<Option<crate::Checksum<'static>>, crate::Error<'static>> {
let pool = apr::Pool::new();
let mut handler: subversion_sys::svn_txdelta_window_handler_t = None;
let mut handler_baton: *mut std::ffi::c_void = std::ptr::null_mut();
unsafe {
subversion_sys::svn_txdelta_to_svndiff3(
&mut handler,
&mut handler_baton,
output.as_mut_ptr(),
svndiff_version,
compression_level,
pool.as_mut_ptr(),
);
}
let empty_source_pool = apr::Pool::new();
let mut empty_source: Option<crate::io::Stream>;
let source_ptr: *mut subversion_sys::svn_stream_t = match source {
Some(s) => s.as_mut_ptr(),
None => {
let s = unsafe {
crate::io::Stream::from_ptr(
subversion_sys::svn_stream_empty(empty_source_pool.as_mut_ptr()),
empty_source_pool,
)
};
empty_source = Some(s);
empty_source.as_mut().unwrap().as_mut_ptr()
}
};
let has_cancel = cancel_func.is_some();
let cancel_baton = cancel_func
.map(|f| Box::into_raw(Box::new(f)) as *mut std::ffi::c_void)
.unwrap_or(std::ptr::null_mut());
let mut checksum: *mut subversion_sys::svn_checksum_t = std::ptr::null_mut();
let err = unsafe {
subversion_sys::svn_txdelta_run(
source_ptr,
target.as_mut_ptr(),
handler,
handler_baton,
checksum_kind.into(),
&mut checksum,
if has_cancel {
Some(crate::wrap_cancel_func)
} else {
None
},
cancel_baton,
pool.as_mut_ptr(), pool.as_mut_ptr(), )
};
if has_cancel && !cancel_baton.is_null() {
unsafe {
drop(Box::from_raw(
cancel_baton as *mut Box<dyn Fn() -> Result<(), crate::Error<'static>>>,
));
}
}
crate::svn_result(err)?;
let result_checksum = if checksum.is_null() {
None
} else {
Some(crate::Checksum::from_raw(checksum))
};
Ok(result_checksum)
}
pub unsafe fn path_driver<F>(
editor: &WrapEditor,
paths: &[&str],
callback: F,
) -> Result<(), crate::Error<'static>>
where
F: FnMut(
Option<*mut std::ffi::c_void>,
&str,
) -> Result<Option<*mut std::ffi::c_void>, crate::Error<'static>>,
{
let pool = apr::Pool::new();
let paths_array = unsafe {
apr_sys::apr_array_make(
pool.as_mut_ptr(),
paths.len() as i32,
std::mem::size_of::<*const i8>() as i32,
)
};
for path in paths {
let path_cstr = std::ffi::CString::new(*path)?;
unsafe {
let elt = apr_sys::apr_array_push(paths_array) as *mut *const i8;
*elt = apr_sys::apr_pstrdup(pool.as_mut_ptr(), path_cstr.as_ptr());
}
}
let mut boxed_callback: Box<
Box<
dyn FnMut(
Option<*mut std::ffi::c_void>,
&str,
) -> Result<Option<*mut std::ffi::c_void>, crate::Error<'static>>,
>,
> = Box::new(Box::new(callback));
let baton_ptr = &mut *boxed_callback as *mut _ as *mut std::ffi::c_void;
extern "C" fn trampoline(
dir_baton: *mut *mut std::ffi::c_void,
parent_baton: *mut std::ffi::c_void,
callback_baton: *mut std::ffi::c_void,
path: *const i8,
_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
unsafe {
let callback = &mut *(callback_baton
as *mut Box<
dyn FnMut(
Option<*mut std::ffi::c_void>,
&str,
)
-> Result<Option<*mut std::ffi::c_void>, crate::Error<'static>>,
>);
let path_str = std::ffi::CStr::from_ptr(path).to_str().unwrap();
let parent = if parent_baton.is_null() {
None
} else {
Some(parent_baton)
};
match callback(parent, path_str) {
Ok(result) => {
if !dir_baton.is_null() {
*dir_baton = result.unwrap_or(std::ptr::null_mut());
}
std::ptr::null_mut()
}
Err(e) => e.into_raw(),
}
}
}
let ret = unsafe {
subversion_sys::svn_delta_path_driver(
editor.editor,
editor.baton,
-1, paths_array,
Some(trampoline),
baton_ptr,
pool.as_mut_ptr(),
)
};
crate::svn_result(ret)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version() {
assert_eq!(super::version().major(), 1);
}
use std::cell::RefCell;
use std::rc::Rc;
struct TestEditor {
operations: Rc<RefCell<Vec<String>>>,
}
impl Editor for TestEditor {
type RootEditor = TestDirectoryEditor;
fn set_target_revision(
&mut self,
revision: Option<crate::Revnum>,
) -> Result<(), crate::Error<'_>> {
self.operations
.borrow_mut()
.push(format!("set_target_revision({:?})", revision.map(|r| r.0)));
Ok(())
}
fn open_root(
&mut self,
base_revision: Option<crate::Revnum>,
) -> Result<TestDirectoryEditor, crate::Error<'_>> {
self.operations
.borrow_mut()
.push(format!("open_root({:?})", base_revision.map(|r| r.0)));
Ok(TestDirectoryEditor {
operations: self.operations.clone(),
})
}
fn close(&mut self) -> Result<(), crate::Error<'_>> {
self.operations.borrow_mut().push("close".to_string());
Ok(())
}
fn abort(&mut self) -> Result<(), crate::Error<'_>> {
self.operations.borrow_mut().push("abort".to_string());
Ok(())
}
}
struct TestDirectoryEditor {
operations: Rc<RefCell<Vec<String>>>,
}
impl DirectoryEditor for TestDirectoryEditor {
type SubDirectory = TestDirectoryEditor;
type File = TestFileEditor;
fn delete_entry(
&mut self,
path: &str,
revision: Option<crate::Revnum>,
) -> Result<(), crate::Error<'_>> {
self.operations.borrow_mut().push(format!(
"delete_entry({}, {:?})",
path,
revision.map(|r| r.0)
));
Ok(())
}
fn add_directory(
&mut self,
path: &str,
copyfrom: Option<(&str, crate::Revnum)>,
) -> Result<TestDirectoryEditor, crate::Error<'_>> {
self.operations.borrow_mut().push(format!(
"add_directory({}, {:?})",
path,
copyfrom.map(|(p, r)| (p, r.0))
));
Ok(TestDirectoryEditor {
operations: self.operations.clone(),
})
}
fn open_directory(
&mut self,
path: &str,
base_revision: Option<crate::Revnum>,
) -> Result<TestDirectoryEditor, crate::Error<'_>> {
self.operations.borrow_mut().push(format!(
"open_directory({}, {:?})",
path,
base_revision.map(|r| r.0)
));
Ok(TestDirectoryEditor {
operations: self.operations.clone(),
})
}
fn change_prop(
&mut self,
name: &str,
value: Option<&[u8]>,
) -> Result<(), crate::Error<'_>> {
self.operations
.borrow_mut()
.push(format!("change_prop({}, {:?})", name, value));
Ok(())
}
fn close(&mut self) -> Result<(), crate::Error<'_>> {
self.operations
.borrow_mut()
.push("close_directory".to_string());
Ok(())
}
fn absent_directory(&mut self, path: &str) -> Result<(), crate::Error<'_>> {
self.operations
.borrow_mut()
.push(format!("absent_directory({})", path));
Ok(())
}
fn add_file(
&mut self,
path: &str,
copyfrom: Option<(&str, crate::Revnum)>,
) -> Result<TestFileEditor, crate::Error<'_>> {
self.operations.borrow_mut().push(format!(
"add_file({}, {:?})",
path,
copyfrom.map(|(p, r)| (p, r.0))
));
Ok(TestFileEditor {
operations: self.operations.clone(),
})
}
fn open_file(
&mut self,
path: &str,
base_revision: Option<crate::Revnum>,
) -> Result<TestFileEditor, crate::Error<'_>> {
self.operations.borrow_mut().push(format!(
"open_file({}, {:?})",
path,
base_revision.map(|r| r.0)
));
Ok(TestFileEditor {
operations: self.operations.clone(),
})
}
fn absent_file(&mut self, path: &str) -> Result<(), crate::Error<'_>> {
self.operations
.borrow_mut()
.push(format!("absent_file({})", path));
Ok(())
}
}
struct TestFileEditor {
operations: Rc<RefCell<Vec<String>>>,
}
impl FileEditor for TestFileEditor {
fn apply_textdelta(
&mut self,
base_checksum: Option<&str>,
) -> Result<
Box<dyn for<'b> Fn(&'b mut TxDeltaWindow) -> Result<(), crate::Error<'static>>>,
crate::Error<'static>,
> {
self.operations
.borrow_mut()
.push(format!("apply_textdelta({:?})", base_checksum));
Ok(Box::new(|_window| Ok(())))
}
fn change_prop(
&mut self,
name: &str,
value: Option<&[u8]>,
) -> Result<(), crate::Error<'static>> {
self.operations
.borrow_mut()
.push(format!("change_file_prop({}, {:?})", name, value));
Ok(())
}
fn close(&mut self, text_checksum: Option<&str>) -> Result<(), crate::Error<'static>> {
self.operations
.borrow_mut()
.push(format!("close_file({:?})", text_checksum));
Ok(())
}
fn apply_textdelta_stream(
&mut self,
base_checksum: Option<&str>,
open_stream: Box<dyn FnOnce() -> Result<TxDeltaStream, crate::Error<'static>>>,
) -> Result<(), crate::Error<'static>> {
self.operations
.borrow_mut()
.push(format!("apply_textdelta_stream({:?})", base_checksum));
let _stream = open_stream()?;
Ok(())
}
}
#[test]
fn test_txdelta_stream() {
let source_data = "Hello, world!";
let target_data = "Hello, Rust world!";
let mut source_buf = crate::io::StringBuf::try_from(source_data).unwrap();
let mut target_buf = crate::io::StringBuf::try_from(target_data).unwrap();
let mut source_stream = crate::io::Stream::from_stringbuf(&mut source_buf);
let mut target_stream = crate::io::Stream::from_stringbuf(&mut target_buf);
let mut txstream = TxDeltaStream::new(&mut source_stream, &mut target_stream);
let mut window_count = 0;
while let Ok(Some(_window)) = txstream.next_window() {
window_count += 1;
if window_count > 100 {
panic!("Too many windows!");
}
}
assert!(
window_count > 0,
"Should have gotten at least one delta window"
);
}
#[test]
fn test_txdelta_functions() {
let source_data = "Original content";
let target_data = "Modified content";
let mut source_buf = crate::io::StringBuf::try_from(source_data).unwrap();
let mut target_buf = crate::io::StringBuf::try_from(target_data).unwrap();
let mut source_stream = crate::io::Stream::from_stringbuf(&mut source_buf);
let mut target_stream = crate::io::Stream::from_stringbuf(&mut target_buf);
let mut txstream = txdelta(&mut source_stream, &mut target_stream);
let window = txstream.next_window();
assert!(window.is_ok(), "Failed to get window: {:?}", window.err());
}
#[test]
fn test_txdelta2_with_checksum() {
let source_data = "Source data";
let target_data = "Target data";
let mut source_buf = crate::io::StringBuf::try_from(source_data).unwrap();
let mut target_buf = crate::io::StringBuf::try_from(target_data).unwrap();
let mut source_stream = crate::io::Stream::from_stringbuf(&mut source_buf);
let mut target_stream = crate::io::Stream::from_stringbuf(&mut target_buf);
let mut txstream = txdelta2(&mut source_stream, &mut target_stream, true);
loop {
let window = txstream.next_window().unwrap();
if window.is_none() {
break;
}
}
let digest = txstream.md5_digest();
let expected_digest = [
0x4a, 0xa8, 0x53, 0x7f, 0x53, 0xf3, 0x29, 0x99, 0x13, 0x78, 0x90, 0x52, 0xa9, 0x2c,
0xd0, 0x73,
];
assert_eq!(digest, expected_digest, "MD5 digest mismatch");
}
#[test]
fn test_apply_delta() {
let source_data = "Source content";
let target_data = "";
let mut source_buf = crate::io::StringBuf::try_from(source_data).unwrap();
let mut target_buf = crate::io::StringBuf::try_from(target_data).unwrap();
let mut source_stream = crate::io::Stream::from_stringbuf(&mut source_buf);
let mut target_stream = crate::io::Stream::from_stringbuf(&mut target_buf);
let result = apply(&mut source_stream, &mut target_stream);
assert!(result.is_ok(), "Failed to apply delta: {:?}", result.err());
let (handler, _baton) = result.unwrap();
assert!(handler.is_some(), "Handler should not be None");
}
#[test]
fn test_apply_textdelta_stream_default() {
struct MinimalFileEditor;
impl FileEditor for MinimalFileEditor {
fn apply_textdelta(
&mut self,
_base_checksum: Option<&str>,
) -> Result<
Box<dyn for<'a> Fn(&'a mut TxDeltaWindow) -> Result<(), crate::Error<'static>>>,
crate::Error<'static>,
> {
Ok(Box::new(|_| Ok(())))
}
fn change_prop(
&mut self,
_name: &str,
_value: Option<&[u8]>,
) -> Result<(), crate::Error<'static>> {
Ok(())
}
fn close(&mut self, _text_checksum: Option<&str>) -> Result<(), crate::Error<'static>> {
Ok(())
}
}
let mut editor = MinimalFileEditor;
let open_stream: Box<dyn FnOnce() -> Result<TxDeltaStream, crate::Error<'static>>> =
Box::new(|| Err(crate::Error::from_message("Not called")));
let result = editor.apply_textdelta_stream(None, open_stream);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("not supported"));
}
#[test]
fn test_apply_textdelta_stream_custom() {
let operations: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
let mut editor = TestFileEditor {
operations: operations.clone(),
};
let open_stream: Box<dyn FnOnce() -> Result<TxDeltaStream, crate::Error<'static>>> =
Box::new(|| Err(crate::Error::from_message("Mock stream error")));
let result = editor.apply_textdelta_stream(Some("abc123"), open_stream);
assert!(result.is_err());
let ops = operations.borrow();
assert!(ops.contains(&"apply_textdelta_stream(Some(\"abc123\"))".to_string()));
}
#[test]
fn test_svndiff_from_streams_full_text() {
let target_data = b"Hello, world!\n";
let mut target_stream = crate::io::Stream::from(&target_data[..]);
let mut output = crate::io::Stream::buffered();
let checksum = svndiff_from_streams(
None, &mut target_stream,
&mut output,
0, -1, crate::ChecksumKind::MD5,
None,
)
.unwrap();
assert!(checksum.is_some(), "expected a checksum");
let mut buf = [0u8; 4];
let mut read_buf = vec![0u8; 1024];
let n = output.read_full(&mut read_buf).unwrap();
assert!(n >= 4, "svndiff output should be at least 4 bytes, got {n}");
buf.copy_from_slice(&read_buf[..4]);
assert_eq!(&buf[..3], b"SVN", "svndiff magic bytes missing");
assert_eq!(buf[3], 0, "expected svndiff version 0");
}
#[test]
fn test_svndiff_from_streams_with_source() {
let source_data = b"Hello, world!\n";
let target_data = b"Hello, Rust!\n";
let mut source_stream = crate::io::Stream::from(&source_data[..]);
let mut target_stream = crate::io::Stream::from(&target_data[..]);
let mut output = crate::io::Stream::buffered();
let checksum = svndiff_from_streams(
Some(&mut source_stream),
&mut target_stream,
&mut output,
0,
-1,
crate::ChecksumKind::MD5,
None,
)
.unwrap();
assert!(checksum.is_some());
let mut read_buf = vec![0u8; 1024];
let n = output.read_full(&mut read_buf).unwrap();
assert!(n >= 4, "svndiff output should be at least 4 bytes");
assert_eq!(&read_buf[..3], b"SVN");
}
#[test]
fn test_editor_set_target_revision_value() {
let operations = Rc::new(RefCell::new(Vec::new()));
let test_editor = TestEditor {
operations: operations.clone(),
};
let mut wrap_editor = WrapEditor::from_rust_editor(test_editor);
wrap_editor.set_target_revision(None).unwrap();
wrap_editor
.set_target_revision(Some(crate::Revnum(100)))
.unwrap();
wrap_editor.close().unwrap();
let ops = operations.borrow();
assert_eq!(ops[0], "set_target_revision(None)");
assert_eq!(ops[1], "set_target_revision(Some(100))");
assert!(ops[1].contains("100"));
}
#[test]
fn test_editor_delete_entry_revision_matters() {
let operations = Rc::new(RefCell::new(Vec::new()));
let test_editor = TestEditor {
operations: operations.clone(),
};
let mut wrap_editor = WrapEditor::from_rust_editor(test_editor);
let mut root = wrap_editor.open_root(None).unwrap();
root.delete_entry("file1.txt", Some(crate::Revnum(50)))
.unwrap();
root.delete_entry("file2.txt", None).unwrap();
root.close().unwrap();
wrap_editor.close().unwrap();
let ops = operations.borrow();
assert!(ops
.iter()
.any(|s| s.contains("file1.txt") && s.contains("50")));
assert!(ops
.iter()
.any(|s| s.contains("file2.txt") && s.contains("None")));
}
#[test]
fn test_apply_textdelta_through_c_vtable() {
let operations = Rc::new(RefCell::new(Vec::new()));
let test_editor = TestEditor {
operations: operations.clone(),
};
let mut wrap_editor = WrapEditor::from_rust_editor(test_editor);
let mut root = wrap_editor.open_root(None).unwrap();
let mut file = root.add_file("test.txt", None).unwrap();
let _handler = file.apply_textdelta(Some("abc123")).unwrap();
let ops = operations.borrow();
assert!(
ops.iter().any(|s| s == "apply_textdelta(Some(\"abc123\"))"),
"apply_textdelta should have been recorded, got: {:?}",
*ops
);
drop(ops);
let _handler2 = file.apply_textdelta(None).unwrap();
let ops = operations.borrow();
assert!(
ops.iter().any(|s| s == "apply_textdelta(None)"),
"apply_textdelta(None) should have been recorded, got: {:?}",
*ops
);
drop(ops);
let raw_handler = file.apply_textdelta_raw(Some("def456")).unwrap();
let ops = operations.borrow();
assert!(
ops.iter().any(|s| s == "apply_textdelta(Some(\"def456\"))"),
"apply_textdelta_raw should route through same trampoline, got: {:?}",
*ops
);
drop(ops);
raw_handler.finish().unwrap();
file.close(None).unwrap();
root.close().unwrap();
wrap_editor.close().unwrap();
}
#[test]
fn test_directory_copyfrom_revision_matters() {
let operations = Rc::new(RefCell::new(Vec::new()));
let test_editor = TestEditor {
operations: operations.clone(),
};
let mut wrap_editor = WrapEditor::from_rust_editor(test_editor);
let mut root = wrap_editor.open_root(None).unwrap();
let mut dir1 = root
.add_directory("dir1", Some(("/src", crate::Revnum(25))))
.unwrap();
dir1.close().unwrap();
root.close().unwrap();
wrap_editor.close().unwrap();
let ops = operations.borrow();
assert!(ops.iter().any(|s| s.contains("dir1") && s.contains("25")));
}
}