Skip to content
Open
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
31 changes: 25 additions & 6 deletions src/stash.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::build::CheckoutBuilder;
use crate::util::{self, Binding};
use crate::{panic, raw, IntoCString, Oid, Signature, StashApplyProgress, StashFlags};
use crate::{panic, raw, Error, IntoCString, Oid, Signature, StashApplyProgress, StashFlags};
use libc::{c_char, c_int, c_void, size_t};
use std::ffi::{c_uint, CStr, CString};
use std::mem;
Expand Down Expand Up @@ -45,11 +45,11 @@ impl<'a> StashSaveOptions<'a> {
}

/// Add to the array of paths patterns to build the stash.
pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut Self {
let s = util::cstring_to_repo_path(pathspec).unwrap();
pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> Result<&mut Self, Error> {

@weihanglo weihanglo Jul 16, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we kinda work towards this though it is still a bit annoying to users. Most of the people won't hit this issue and they need to take care of Result. I am a bit unsure.

Should we just make it an expect and document it would panic?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, but I think it would be nicer to return a result - if the library is able to avoid a panic without being left in a problematic state, why not do so?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and panic should be use mostly as a program invariant. This is kinda like a bad input. However, we can also see this as an invariant of these functions.

I am not sure though. I think technically it is more correct, though sacrifice ergonomic. And it may also be confusing as API caller now don't know how they should handle the error or not unless looking at the function body and realize it only checks bad cstring.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also switch it to requiring that the provided value be a CString rather than just something that can be converted to one?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That also loses ergonomic in another way 😞.

@DanielEScherzer DanielEScherzer Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i figured a result was more ergonomic than requiring a CString, because I think panicking is worse than either

let s = util::cstring_to_repo_path(pathspec)?;
self.pathspec_ptrs.push(s.as_ptr());
self.pathspec.push(s);
self
Ok(self)
}

/// Acquire a pointer to the underlying raw options.
Expand Down Expand Up @@ -215,7 +215,7 @@ extern "C" fn stash_apply_progress_cb(
mod tests {
use crate::stash::{StashApplyOptions, StashSaveOptions};
use crate::test::repo_init;
use crate::{IndexAddOption, Repository, StashFlags, Status};
use crate::{IndexAddOption, Repository, Signature, StashFlags, Status};
use std::fs;
use std::path::{Path, PathBuf};

Expand Down Expand Up @@ -338,7 +338,7 @@ mod tests {
assert_eq!(repo.statuses(None).unwrap().len(), 2);

let mut opt = StashSaveOptions::new(signature);
opt.pathspec("file_a");
opt.pathspec("file_a").expect("Should be a valid path spec");
repo.stash_save_ext(Some(&mut opt)).unwrap();

assert_eq!(repo.statuses(None).unwrap().len(), 0);
Expand All @@ -347,4 +347,23 @@ mod tests {

assert_eq!(repo.statuses(None).unwrap().len(), 1);
}

#[test]
fn test_stash_invalid_path() {
let sig = Signature::now("name", "email").unwrap();
let mut opts = StashSaveOptions::new(sig);
// Cannot use unwrap_err() because StashSaveOptions does not implement Debug
let result = match opts.pathspec("abc\x00xyz") {
Ok(_) => panic!("Expected an error"),
Err(e) => e,
};
assert_eq!(
crate::Error::new(
crate::ErrorCode::GenericError,
crate::ErrorClass::None,
"data contained a nul byte that could not be represented as a string"
),
result
);
}
}