Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ oxen push origin main # Push to remote
- After changing any Rust or Python code, verify that Rust tests pass with `bin/test-rust` and Python tests pass with `bin/test-rust -p`
- When updating a dependency, prefer updating to the latest stable version.
- Any new or changed Rust code that touches IO (file system, network, etc.) should be async code. Instead of std::io or std::fs, use equivalents from tokio. When an external dependency doesn't support async, use tokio's spawn_blocking functionality.
- Streamed IO (anything reading or writing through an `AsyncRead`/`AsyncWrite` whose total length isn't bounded ahead of time) must use a large buffer rather than rely on `tokio::io::copy`'s 8 KB default. Wrap the write side in `tokio::io::BufWriter::with_capacity(10 * 1024 * 1024, ...)` (and the read side in `BufReader` if the source isn't already buffered), and remember to explicitly `flush().await?` the `BufWriter` before any downstream `sync_all`/rename/checksum step — `BufWriter`'s `Drop` does **not** auto-flush, so unflushed bytes are silently dropped. The canonical example is the S3 store's local-cache path in `crates/lib/src/storage/s3.rs` (`store_version_to_path`).
- oxen-server operations should never touch a local checkout on disk when doing operations initiated by its API.
- Always use `metadata.is_dir()` instead of `path.is_dir()`. `path.is_dir()` follows symlinks, which Oxen does not track — using it risks descending into directories outside the working tree (or into cycles via cyclic links).
- Oxen does not track symlinks. New code that traverses the working tree should check `metadata.is_symlink()` and skip rather than resolve, follow, or record symlinks.
Expand Down
33 changes: 22 additions & 11 deletions crates/lib/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,21 @@ pub enum OxenError {
#[error("{0}")]
IO(#[from] io::Error),

/// A `create`-shaped filesystem syscall failed at the given path (e.g. opening with
/// `O_CREAT`, creating a directory tree). Carries the underlying [`io::Error`] so
/// callers can match on `ErrorKind` (e.g. `AlreadyExists` for `O_EXCL` writers).
#[error("Could not create file: {0:?}: {1}")]
FileCreate(PathBuf, #[source] io::Error),

/// A rename syscall failed when moving `src` to `dst`.
#[error("Could not rename file from {src:?} to {dst:?}: {source}")]
FileRename {
src: PathBuf,
dst: PathBuf,
#[source]
source: io::Error,
},

/// Encountered when authentication fails. Contains the authentication error message.
#[error("Authentication failed: {0}")]
Authentication(StringError),
Expand Down Expand Up @@ -770,11 +785,7 @@ impl OxenError {
}

pub fn file_create_error(path: impl AsRef<Path>, error: std::io::Error) -> OxenError {
OxenError::basic_str(format!(
"Could not create file: {:?} error {:?}",
path.as_ref(),
error
))
OxenError::FileCreate(path.as_ref().to_path_buf(), error)
}

pub fn file_open_error(path: impl AsRef<Path>, error: std::io::Error) -> OxenError {
Expand Down Expand Up @@ -816,13 +827,13 @@ impl OxenError {
pub fn file_rename_error(
src: impl AsRef<Path>,
dst: impl AsRef<Path>,
err: impl std::fmt::Debug,
source: std::io::Error,
) -> OxenError {
OxenError::basic_str(format!(
"File rename error: {err:?}\nCould not move from `{:?}` to `{:?}`",
src.as_ref(),
dst.as_ref()
))
OxenError::FileRename {
src: src.as_ref().to_path_buf(),
dst: dst.as_ref().to_path_buf(),
source,
}
}

pub fn cannot_overwrite_files(paths: &[PathBuf]) -> OxenError {
Expand Down
Loading
Loading