diff --git a/client/src/context.rs b/client/src/context.rs index e1a41b37..59cadcce 100644 --- a/client/src/context.rs +++ b/client/src/context.rs @@ -18,9 +18,7 @@ fn parse_revision(_py: Python, rev: &Bound) -> PyResult() { - let revnum = subvertpy_util::to_revnum(n).ok_or_else(|| { - PyErr::new::("Invalid revision number") - })?; + let revnum = subvertpy_util::to_revnum_or_head(n); Ok(subversion::Revision::Number(revnum)) } else { Err(PyErr::new::( @@ -814,12 +812,7 @@ impl Client { }; let base_rev = if let Some(rev) = base_revision_for_url { - subvertpy_util::to_revnum(rev).ok_or_else(|| { - PyErr::new::(format!( - "Invalid revision number: {}", - rev - )) - })? + subvertpy_util::to_revnum_or_head(rev) } else { subversion::Revnum::invalid() }; diff --git a/ra/src/reporter.rs b/ra/src/reporter.rs index 0f284155..1d560376 100644 --- a/ra/src/reporter.rs +++ b/ra/src/reporter.rs @@ -75,8 +75,7 @@ impl Reporter { PyErr::new::("Reporter has been consumed") })?; - let rev = subvertpy_util::to_revnum(revision) - .ok_or_else(|| PyErr::new::("Invalid revision"))?; + let rev = subvertpy_util::to_revnum_or_head(revision); let svn_depth = match depth { Some(d) => py_depth_to_svn(d)?, @@ -125,8 +124,7 @@ impl Reporter { PyErr::new::("Reporter has been consumed") })?; - let rev = subvertpy_util::to_revnum(revision) - .ok_or_else(|| PyErr::new::("Invalid revision"))?; + let rev = subvertpy_util::to_revnum_or_head(revision); let svn_depth = match depth { Some(d) => py_depth_to_svn(d)?, diff --git a/ra/src/session.rs b/ra/src/session.rs index c8b2290b..09b58c62 100644 --- a/ra/src/session.rs +++ b/ra/src/session.rs @@ -157,8 +157,9 @@ impl RemoteAccess { }); } + let url = subversion::uri::canonicalize_uri(url).map_err(|e| svn_err_to_py(e))?; let (session, _corrected_url, _repos_root) = subversion::ra::Session::open( - url, + &url, None, // config_dir Some(callbacks_ref), None, // config @@ -211,7 +212,8 @@ impl RemoteAccess { /// Reparent the session to a new URL fn reparent(&mut self, url: &str) -> PyResult<()> { - self.session.reparent(url).map_err(|e| svn_err_to_py(e)) + let url = subversion::uri::canonicalize_uri(url).map_err(|e| svn_err_to_py(e))?; + self.session.reparent(&url).map_err(|e| svn_err_to_py(e)) } /// Check if the repository has a capability @@ -223,15 +225,12 @@ impl RemoteAccess { /// Check the type of a path fn check_path(&mut self, path: &str, revnum: i64) -> PyResult { - let rev = subvertpy_util::to_revnum(revnum).ok_or_else(|| { - PyErr::new::("Invalid revision number") - })?; - - let path = path.trim_start_matches('/').trim_end_matches('/'); + let rev = subvertpy_util::to_revnum_or_head(revnum); + let path = subvertpy_util::to_relpath(path)?; let node_kind = self .session - .check_path(path, rev) + .check_path(path.as_str(), rev) .map_err(|e| svn_err_to_py(e))?; Ok(node_kind_to_py(node_kind)) @@ -239,9 +238,7 @@ impl RemoteAccess { /// Get revision properties fn rev_proplist(&mut self, revnum: i64) -> PyResult> { - let rev = subvertpy_util::to_revnum(revnum).ok_or_else(|| { - PyErr::new::("Invalid revision number") - })?; + let rev = subvertpy_util::to_revnum_or_head(revnum); let props = self .session @@ -262,9 +259,7 @@ impl RemoteAccess { value: Option>, old_value: Option>, ) -> PyResult<()> { - let rev = subvertpy_util::to_revnum(revnum).ok_or_else(|| { - PyErr::new::("Invalid revision number") - })?; + let rev = subvertpy_util::to_revnum_or_head(revnum); let extract_bytes = |v: &Bound| -> PyResult> { if let Ok(b) = v.cast::() { @@ -318,7 +313,7 @@ impl RemoteAccess { stream: Bound, revnum: Option, ) -> PyResult<(Option, pyo3::Py)> { - let path = path.trim_start_matches('/'); + let path = subvertpy_util::to_relpath(path)?; let rev = revnum .and_then(|r| subvertpy_util::to_revnum(r)) @@ -330,7 +325,7 @@ impl RemoteAccess { let (fetched_rev, props) = self .session - .get_file(path, rev, &mut svn_stream) + .get_file(path.as_str(), rev, &mut svn_stream) .map_err(|e| svn_err_to_py(e))?; svn_stream.close().map_err(|e| svn_err_to_py(e))?; @@ -361,11 +356,8 @@ impl RemoteAccess { revision: i64, fields: Option, ) -> PyResult<(pyo3::Py, i64, pyo3::Py)> { - let rev = subvertpy_util::to_revnum(revision).ok_or_else(|| { - PyErr::new::("Invalid revision number") - })?; - - let path = path.trim_start_matches('/'); + let rev = subvertpy_util::to_revnum_or_head(revision); + let path = subvertpy_util::to_relpath(path)?; let dirent_fields = match fields { Some(f) => subversion::DirentField::from_bits_truncate(f as u32), @@ -374,7 +366,7 @@ impl RemoteAccess { let (fetched_rev, dirents, props) = self .session - .get_dir(path, rev, dirent_fields) + .get_dir(path.as_str(), rev, dirent_fields) .map_err(|e| svn_err_to_py(e))?; Python::attach(|py| { @@ -408,13 +400,13 @@ impl RemoteAccess { /// Get file/directory status information fn stat(&mut self, path: &str, revnum: i64) -> PyResult> { - let rev = subvertpy_util::to_revnum(revnum).ok_or_else(|| { - PyErr::new::("Invalid revision number") - })?; + let rev = subvertpy_util::to_revnum_or_head(revnum); + let path = subvertpy_util::to_relpath(path)?; - let path = path.trim_start_matches('/').trim_end_matches('/'); - - let dirent = self.session.stat(path, rev).map_err(|e| svn_err_to_py(e))?; + let dirent = self + .session + .stat(path.as_str(), rev) + .map_err(|e| svn_err_to_py(e))?; Python::attach(|py| { let dict = pyo3::types::PyDict::new(py); @@ -448,9 +440,7 @@ impl RemoteAccess { for (key, value) in path_revs.iter() { let path_str = subvertpy_util::py_to_svn_string(&key)?; let revnum: i64 = value.extract()?; - let rev = subvertpy_util::to_revnum(revnum).ok_or_else(|| { - PyErr::new::("Invalid revision number") - })?; + let rev = subvertpy_util::to_revnum_or_head(revnum); path_rev_map.insert(path_str, rev); } @@ -576,8 +566,11 @@ impl RemoteAccess { /// Get lock for a path (singular) fn get_lock(&mut self, path: &str) -> PyResult>> { - let path = path.strip_prefix('/').unwrap_or(path); - let lock_opt = self.session.get_lock(path).map_err(|e| svn_err_to_py(e))?; + let path = subvertpy_util::to_relpath(path)?; + let lock_opt = self + .session + .get_lock(path.as_str()) + .map_err(|e| svn_err_to_py(e))?; match lock_opt { Some(lock) => Python::attach(|py| { @@ -655,16 +648,16 @@ impl RemoteAccess { include_merged_revisions: Option, revprops: Option>, ) -> PyResult<()> { - let start_rev = subvertpy_util::to_revnum(start).ok_or_else(|| { - PyErr::new::("Invalid start revision") - })?; - let end_rev = subvertpy_util::to_revnum(end).ok_or_else(|| { - PyErr::new::("Invalid end revision") - })?; + let start_rev = subvertpy_util::to_revnum_or_head(start); + let end_rev = subvertpy_util::to_revnum_or_head(end); // When paths is None, pass [""] to SVN (meaning "the session root"), // matching the old C subvertpy behavior. - let paths = paths.unwrap_or_else(|| vec![String::new()]); + let paths: Vec = paths + .unwrap_or_else(|| vec![String::new()]) + .iter() + .map(|p| subvertpy_util::to_relpath(p)) + .collect::>>()?; let path_refs: Vec<&str> = paths.iter().map(|s| s.as_str()).collect(); let revprop_strs: Option> = revprops; @@ -729,16 +722,16 @@ impl RemoteAccess { let py = slf.py(); let session_ref = slf.clone().unbind(); - let start_rev = subvertpy_util::to_revnum(start).ok_or_else(|| { - PyErr::new::("Invalid start revision") - })?; - let end_rev = subvertpy_util::to_revnum(end).ok_or_else(|| { - PyErr::new::("Invalid end revision") - })?; + let start_rev = subvertpy_util::to_revnum_or_head(start); + let end_rev = subvertpy_util::to_revnum_or_head(end); // When paths is None, pass [""] to SVN (meaning "the session root"), // matching the old C subvertpy behavior. - let paths = paths.unwrap_or_else(|| vec![String::new()]); + let paths: Vec = paths + .unwrap_or_else(|| vec![String::new()]) + .iter() + .map(|p| subvertpy_util::to_relpath(p)) + .collect::>>()?; let path_refs: Vec<&str> = paths.iter().map(|s| s.as_str()).collect(); let revprop_strs: Option> = revprops; @@ -792,21 +785,18 @@ impl RemoteAccess { peg_revision: i64, location_revisions: Vec, ) -> PyResult> { - let peg_rev = subvertpy_util::to_revnum(peg_revision).ok_or_else(|| { - PyErr::new::("Invalid peg revision number") - })?; + let path = subvertpy_util::to_relpath(path)?; + let peg_rev = subvertpy_util::to_revnum_or_head(peg_revision); let mut revnums = Vec::new(); for revnum in location_revisions { - let rev = subvertpy_util::to_revnum(revnum).ok_or_else(|| { - PyErr::new::("Invalid location revision number") - })?; + let rev = subvertpy_util::to_revnum_or_head(revnum); revnums.push(rev); } let locations = self .session - .get_locations(path, peg_rev, &revnums) + .get_locations(path.as_str(), peg_rev, &revnums) .map_err(|e| svn_err_to_py(e))?; Python::attach(|py| { @@ -827,15 +817,10 @@ impl RemoteAccess { end_revision: i64, receiver: Bound, ) -> PyResult<()> { - let peg_rev = subvertpy_util::to_revnum(peg_revision).ok_or_else(|| { - PyErr::new::("Invalid peg revision") - })?; - let start_rev = subvertpy_util::to_revnum(start_revision).ok_or_else(|| { - PyErr::new::("Invalid start revision") - })?; - let end_rev = subvertpy_util::to_revnum(end_revision).ok_or_else(|| { - PyErr::new::("Invalid end revision") - })?; + let path = subvertpy_util::to_relpath(path)?; + let peg_rev = subvertpy_util::to_revnum_or_head(peg_revision); + let start_rev = subvertpy_util::to_revnum_or_head(start_revision); + let end_rev = subvertpy_util::to_revnum_or_head(end_revision); let py_receiver = receiver.clone(); let location_receiver = @@ -864,7 +849,13 @@ impl RemoteAccess { }; self.session - .get_location_segments(path, peg_rev, start_rev, end_rev, &location_receiver) + .get_location_segments( + path.as_str(), + peg_rev, + start_rev, + end_rev, + &location_receiver, + ) .map_err(|e| svn_err_to_py(e))?; Ok(()) @@ -880,12 +871,9 @@ impl RemoteAccess { handler: Bound, include_merged_revisions: Option, ) -> PyResult<()> { - let start_rev = subvertpy_util::to_revnum(start).ok_or_else(|| { - PyErr::new::("Invalid start revision") - })?; - let end_rev = subvertpy_util::to_revnum(end).ok_or_else(|| { - PyErr::new::("Invalid end revision") - })?; + let path = subvertpy_util::to_relpath(path)?; + let start_rev = subvertpy_util::to_revnum_or_head(start); + let end_rev = subvertpy_util::to_revnum_or_head(end); let py_handler = handler.clone(); let file_rev_callback = |path: &str, @@ -923,7 +911,7 @@ impl RemoteAccess { self.session .get_file_revs( - path, + path.as_str(), start_rev, end_rev, include_merged_revisions.unwrap_or(false), @@ -1028,11 +1016,8 @@ impl RemoteAccess { ignore_ancestry: bool, text_deltas: bool, ) -> PyResult { - let rev = subvertpy_util::to_revnum(revision).ok_or_else(|| { - PyErr::new::("Invalid revision number") - })?; - - let diff_target = diff_target.trim_start_matches('/'); + let rev = subvertpy_util::to_revnum_or_head(revision); + let diff_target = subvertpy_util::to_relpath(diff_target)?; let py_editor = crate::py_editor::PyEditorWrapper::new(diff_editor); let wrap_editor = py_editor.into_wrap_editor(); @@ -1057,7 +1042,7 @@ impl RemoteAccess { let session_ptr = &mut slf.borrow_mut().session as *mut subversion::ra::Session<'static>; let raw_reporter = (*session_ptr) - .do_diff(rev, diff_target, &mut options) + .do_diff(rev, diff_target.as_str(), &mut options) .map_err(|e| svn_err_to_py(e))?; // Transmute the reporter's lifetime from 's to 'static @@ -1097,11 +1082,8 @@ impl RemoteAccess { send_copyfrom_args: bool, ignore_ancestry: bool, ) -> PyResult { - let rev = subvertpy_util::to_revnum(revision).ok_or_else(|| { - PyErr::new::("Invalid revision number") - })?; - - let update_target = update_target.trim_start_matches('/'); + let rev = subvertpy_util::to_revnum_or_head(revision); + let update_target = subvertpy_util::to_relpath(update_target)?; let py_editor = crate::py_editor::PyEditorWrapper::new(update_editor); let wrap_editor = py_editor.into_wrap_editor(); @@ -1123,7 +1105,7 @@ impl RemoteAccess { let raw_reporter = (*session_ptr) .do_update( rev, - update_target, + update_target.as_str(), depth, send_copyfrom_args, ignore_ancestry, @@ -1159,11 +1141,8 @@ impl RemoteAccess { send_copyfrom_args: bool, ignore_ancestry: bool, ) -> PyResult { - let rev = subvertpy_util::to_revnum(revision).ok_or_else(|| { - PyErr::new::("Invalid revision number") - })?; - - let switch_target = switch_target.trim_start_matches('/'); + let rev = subvertpy_util::to_revnum_or_head(revision); + let switch_target = subvertpy_util::to_relpath(switch_target)?; let py_editor = crate::py_editor::PyEditorWrapper::new(switch_editor); let wrap_editor = py_editor.into_wrap_editor(); @@ -1185,7 +1164,7 @@ impl RemoteAccess { let raw_reporter = (*session_ptr) .do_switch( rev, - switch_target, + switch_target.as_str(), depth, switch_url, send_copyfrom_args, @@ -1219,12 +1198,8 @@ impl RemoteAccess { editor: Py, send_deltas: bool, ) -> PyResult<()> { - let rev = subvertpy_util::to_revnum(revision).ok_or_else(|| { - PyErr::new::("Invalid revision number") - })?; - let lwm = subvertpy_util::to_revnum(low_water_mark).ok_or_else(|| { - PyErr::new::("Invalid low water mark revision") - })?; + let rev = subvertpy_util::to_revnum_or_head(revision); + let lwm = subvertpy_util::to_revnum_or_head(low_water_mark); let py_editor = crate::py_editor::PyEditorWrapper::new(editor); let mut wrap_editor = py_editor.into_wrap_editor(); @@ -1249,15 +1224,9 @@ impl RemoteAccess { cbs: Bound, send_deltas: bool, ) -> PyResult<()> { - let start_rev = subvertpy_util::to_revnum(start_revision).ok_or_else(|| { - PyErr::new::("Invalid start revision") - })?; - let end_rev = subvertpy_util::to_revnum(end_revision).ok_or_else(|| { - PyErr::new::("Invalid end revision") - })?; - let lwm = subvertpy_util::to_revnum(low_water_mark).ok_or_else(|| { - PyErr::new::("Invalid low water mark revision") - })?; + let start_rev = subvertpy_util::to_revnum_or_head(start_revision); + let end_rev = subvertpy_util::to_revnum_or_head(end_revision); + let lwm = subvertpy_util::to_revnum_or_head(low_water_mark); if cbs.len() != 2 { return Err(PyErr::new::( @@ -1336,9 +1305,7 @@ impl RemoteAccess { include_descendants: bool, ) -> PyResult>> { let rev = match revision { - Some(r) => subvertpy_util::to_revnum(r).ok_or_else(|| { - PyErr::new::("Invalid revision number") - })?, + Some(r) => subvertpy_util::to_revnum_or_head(r), None => self .session .get_latest_revnum() diff --git a/repos/src/filesystem.rs b/repos/src/filesystem.rs index 48dbdcec..e1cdc962 100644 --- a/repos/src/filesystem.rs +++ b/repos/src/filesystem.rs @@ -30,9 +30,7 @@ impl FileSystem { /// Get a revision root for a specific revision fn get_revision_root(&self, revnum: i64) -> PyResult { - let rev = subvertpy_util::to_revnum(revnum).ok_or_else(|| { - PyErr::new::("Invalid revision number") - })?; + let rev = subvertpy_util::to_revnum_or_head(revnum); let root = self.fs.revision_root(rev).map_err(|e| svn_err_to_py(e))?; @@ -41,9 +39,7 @@ impl FileSystem { /// Get revision properties for a specific revision fn get_revision_proplist(&self, revnum: i64) -> PyResult> { - let rev = subvertpy_util::to_revnum(revnum).ok_or_else(|| { - PyErr::new::("Invalid revision number") - })?; + let rev = subvertpy_util::to_revnum_or_head(revnum); let props = self .fs diff --git a/subvertpy_util/src/lib.rs b/subvertpy_util/src/lib.rs index 1541c5ed..a8105724 100644 --- a/subvertpy_util/src/lib.rs +++ b/subvertpy_util/src/lib.rs @@ -30,6 +30,22 @@ pub fn to_revnum(rev: i64) -> Option { } } +/// Convert a Python revision number (i64) to a Revnum. +/// Negative values (typically -1) are mapped to Revnum::invalid(), +/// which the SVN C API interprets as HEAD. +pub fn to_revnum_or_head(rev: i64) -> subversion::Revnum { + if rev < 0 { + subversion::Revnum::invalid() + } else { + subversion::Revnum::from(rev as u64) + } +} + +/// Canonicalize a string as an SVN relative path. +pub fn to_relpath(path: &str) -> PyResult { + subversion::ra::RelPath::canonicalize(path).map_err(|e| error::svn_err_to_py(e)) +} + /// Convert a Python object to an SVN path or URL /// /// This handles both filesystem paths (dirents) and URLs (URIs), diff --git a/wc/src/lib.rs b/wc/src/lib.rs index 8444b469..9265e1dc 100644 --- a/wc/src/lib.rs +++ b/wc/src/lib.rs @@ -64,11 +64,15 @@ fn ensure_adm( depth: i32, ) -> PyResult<()> { let path_str = subvertpy_util::py_to_svn_abspath(path)?; - let repos_root = repos.unwrap_or(url); + let url = subversion::uri::canonicalize_uri(url).map_err(svn_err_to_py)?; + let repos_root = match repos { + Some(r) => subversion::uri::canonicalize_uri(r).map_err(svn_err_to_py)?, + None => url.clone(), + }; let svn_depth = context::depth_from_py(depth); let revnum = subvertpy_util::to_revnum(rev).unwrap_or(subversion::Revnum::invalid()); let mut ctx = subversion::wc::Context::new().map_err(svn_err_to_py)?; - ctx.ensure_adm(&path_str, url, repos_root, uuid, revnum, svn_depth) + ctx.ensure_adm(&path_str, &url, &repos_root, uuid, revnum, svn_depth) .map_err(svn_err_to_py) }