Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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