| Crates.io | svn |
| lib.rs | svn |
| version | 0.1.6 |
| created_at | 2025-12-28 10:31:59.885629+00 |
| updated_at | 2026-01-06 06:16:37.594358+00 |
| description | Async client for Subversion's svn:// (ra_svn) protocol. |
| homepage | https://github.com/lvillis/svn-rs |
| repository | https://github.com/lvillis/svn-rs |
| max_upload_size | |
| id | 2008571 |
| size | 904,267 |
🇺🇸 English · 🇨🇳 䏿–‡       |     Table of Contents
svn-rsA Rust async client for Subversion svn:// (ra_svn) — a modern alternative to libsvn_ra_svn.
svn:// (ra_svn) client (no working copy).svn+ssh:// via SSH tunnel (ssh feature; runs svnserve -t over SSH).RaSvnClient / RaSvnSession.code/message/file/line) with command context.serde feature for public data types.cyrus-sasl feature for Cyrus SASL auth + negotiated SASL security
layer (requires a system-provided libsasl2 at runtime).Add the crate:
cargo add svn
Or in Cargo.toml:
[dependencies]
svn = "0.1"
You also need an async runtime. The examples below use tokio:
[dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
The RaSvnClient type is a reusable configuration; use it to create a connected
RaSvnSession (which owns a single TCP connection).
use std::time::Duration;
use svn::{RaSvnClient, SvnUrl};
#[tokio::main]
async fn main() -> svn::Result<()> {
let url = SvnUrl::parse("svn://example.com/repo")?;
let client = RaSvnClient::new(url, None, None)
.with_connect_timeout(Duration::from_secs(10))
.with_read_timeout(Duration::from_secs(30))
.with_write_timeout(Duration::from_secs(30));
let mut session = client.open_session().await?;
let latest = session.get_latest_rev().await?;
println!("HEAD = {latest}");
Ok(())
}
RaSvnClient is cheap to clone and provides builder-style methods:
with_connect_timeout(Duration)with_read_timeout(Duration)with_write_timeout(Duration)with_ra_client(String) (sent during handshake)In general, prefer reusing a single RaSvnSession for multiple operations to
avoid repeated reconnect + handshake.
This crate supports svn:// authentication mechanisms commonly offered by
svnserve:
ANONYMOUSPLAIN (username + password)CRAM-MD5 (username + password)Pass username/password when creating RaSvnClient. If the server requires an
unsupported mechanism, operations return SvnError::AuthUnavailable.
To enable Cyrus SASL (and the optional SASL security layer), enable the
cyrus-sasl feature (requires libsasl2 installed on the system at runtime):
svn = { version = "0.1", features = ["cyrus-sasl"] }
Notes:
cyrus-sasl, this crate dynamically loads the system Cyrus SASL library
(libsasl2) at runtime. If it is not available, SASL authentication is
unavailable and requests may fail with SvnError::AuthUnavailable.cyrus-sasl is disabled (the default), the crate stays unsafe-free.svn+ssh:// (SSH tunnel)Enable the ssh feature to connect using svn+ssh:// URLs (runs svnserve -t
over SSH using russh):
svn = { version = "0.1", features = ["ssh"] }
use std::path::PathBuf;
use svn::{RaSvnClient, SshAuth, SshConfig, SvnUrl};
# #[tokio::main] async fn main() -> svn::Result<()> {
let url = SvnUrl::parse("svn+ssh://example.com/repo")?;
let ssh = SshConfig::new(SshAuth::KeyFile {
path: PathBuf::from("~/.ssh/id_ed25519"),
passphrase: None,
});
let client = RaSvnClient::new(url, None, None).with_ssh_config(ssh);
let mut session = client.open_session().await?;
let head = session.get_latest_rev().await?;
println!("{head}");
# Ok(()) }
This crate focuses on ra_svn protocol v2 and currently supports:
get-latest-rev, get-dated-rev, get-file, get-dir, log, list,
check-path, stat, get-mergeinfo, get-deleted-rev, get-locations,
get-location-segments, get-file-revs, rev-prop, rev-proplist,
proplist, propget, get-iprops, locks listing (get-lock, get-locks).update, switch, status, diff, replay,
replay-range.change-rev-prop, change-rev-prop2, lock/unlock (including
*-many), and a low-level commit API driven by EditorCommand.For full API docs and examples, see https://docs.rs/svn.
All APIs return svn::Result<T> (an alias for Result<T, SvnError>). Server-side
failures are returned as SvnError::Server(ServerError) and include a structured
error chain and command context.
use svn::{RaSvnClient, SvnError, SvnUrl};
#[tokio::main]
async fn main() {
let url = SvnUrl::parse("svn://example.com/repo").unwrap();
let client = RaSvnClient::new(url, None, None);
let err = client.get_latest_rev().await.unwrap_err();
match err {
SvnError::Server(server) => {
eprintln!("server: {server}");
for item in &server.chain {
eprintln!(" code={} msg={:?} file={:?} line={:?}", item.code, item.message, item.file, item.line);
}
}
other => eprintln!("error: {other}"),
}
}
This crate uses tracing for debug logging. Enable logs in your application
(for example with tracing-subscriber) and set an appropriate filter:
RUST_LOG=svn=debug
ra_svn v2 (svn://, and svn+ssh:// with the ssh feature).svn://[::1]/repo).1.92.0 (see Cargo.toml).serde support via the serde feature.cyrus-sasl (runtime libsasl2).ssh (russh).svn:// is plain TCP (no native TLS).svn+ssh:// uses SSH for transport encryption and authentication.PLAIN sends credentials without encryption unless you use a secure tunnel
(VPN / SSH port forwarding / stunnel) or negotiate a SASL security layer.CRAM-MD5, repository traffic is still unencrypted unless a tunnel
or SASL security layer is used.Unit tests and property tests:
cargo test --all-features
Interop tests against a real svnserve (requires svn, svnadmin, svnserve):
SVN_INTEROP=1 cargo test --all-features --test interop_svnserve -- --nocapture
ssh feature supports a subset of ~/.ssh/config and ssh-agent (no ProxyJump/ProxyCommand).Licensed under the MIT license. See LICENSE.