summaryrefslogtreecommitdiffstats
path: root/vendor/git2/src/stash.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/git2/src/stash.rs')
-rw-r--r--vendor/git2/src/stash.rs348
1 files changed, 348 insertions, 0 deletions
diff --git a/vendor/git2/src/stash.rs b/vendor/git2/src/stash.rs
new file mode 100644
index 0000000..ea898e4
--- /dev/null
+++ b/vendor/git2/src/stash.rs
@@ -0,0 +1,348 @@
+use crate::build::CheckoutBuilder;
+use crate::util::{self, Binding};
+use crate::{panic, raw, 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;
+
+/// Stash application options structure
+pub struct StashSaveOptions<'a> {
+ message: Option<CString>,
+ flags: Option<StashFlags>,
+ stasher: Signature<'a>,
+ pathspec: Vec<CString>,
+ pathspec_ptrs: Vec<*const c_char>,
+ raw_opts: raw::git_stash_save_options,
+}
+
+impl<'a> StashSaveOptions<'a> {
+ /// Creates a default
+ pub fn new(stasher: Signature<'a>) -> Self {
+ let mut opts = Self {
+ message: None,
+ flags: None,
+ stasher,
+ pathspec: Vec::new(),
+ pathspec_ptrs: Vec::new(),
+ raw_opts: unsafe { mem::zeroed() },
+ };
+ assert_eq!(
+ unsafe {
+ raw::git_stash_save_options_init(
+ &mut opts.raw_opts,
+ raw::GIT_STASH_SAVE_OPTIONS_VERSION,
+ )
+ },
+ 0
+ );
+ opts
+ }
+
+ /// Customize optional `flags` field
+ pub fn flags(&mut self, flags: Option<StashFlags>) -> &mut Self {
+ self.flags = flags;
+ self
+ }
+
+ /// 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();
+ self.pathspec_ptrs.push(s.as_ptr());
+ self.pathspec.push(s);
+ self
+ }
+
+ /// Acquire a pointer to the underlying raw options.
+ ///
+ /// This function is unsafe as the pointer is only valid so long as this
+ /// structure is not moved, modified, or used elsewhere.
+ pub unsafe fn raw(&mut self) -> *const raw::git_stash_save_options {
+ self.raw_opts.flags = self.flags.unwrap_or_else(StashFlags::empty).bits() as c_uint;
+ self.raw_opts.message = crate::call::convert(&self.message);
+ self.raw_opts.paths.count = self.pathspec_ptrs.len() as size_t;
+ self.raw_opts.paths.strings = self.pathspec_ptrs.as_ptr() as *mut _;
+ self.raw_opts.stasher = self.stasher.raw();
+
+ &self.raw_opts as *const _
+ }
+}
+
+/// Stash application progress notification function.
+///
+/// Return `true` to continue processing, or `false` to
+/// abort the stash application.
+// FIXME: This probably should have been pub(crate) since it is not used anywhere.
+pub type StashApplyProgressCb<'a> = dyn FnMut(StashApplyProgress) -> bool + 'a;
+
+/// This is a callback function you can provide to iterate over all the
+/// stashed states that will be invoked per entry.
+// FIXME: This probably should have been pub(crate) since it is not used anywhere.
+pub type StashCb<'a> = dyn FnMut(usize, &str, &Oid) -> bool + 'a;
+
+/// Stash application options structure
+pub struct StashApplyOptions<'cb> {
+ progress: Option<Box<StashApplyProgressCb<'cb>>>,
+ checkout_options: Option<CheckoutBuilder<'cb>>,
+ raw_opts: raw::git_stash_apply_options,
+}
+
+impl<'cb> Default for StashApplyOptions<'cb> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<'cb> StashApplyOptions<'cb> {
+ /// Creates a default set of merge options.
+ pub fn new() -> StashApplyOptions<'cb> {
+ let mut opts = StashApplyOptions {
+ progress: None,
+ checkout_options: None,
+ raw_opts: unsafe { mem::zeroed() },
+ };
+ assert_eq!(
+ unsafe { raw::git_stash_apply_init_options(&mut opts.raw_opts, 1) },
+ 0
+ );
+ opts
+ }
+
+ /// Set stash application flag to GIT_STASH_APPLY_REINSTATE_INDEX
+ pub fn reinstantiate_index(&mut self) -> &mut StashApplyOptions<'cb> {
+ self.raw_opts.flags = raw::GIT_STASH_APPLY_REINSTATE_INDEX as u32;
+ self
+ }
+
+ /// Options to use when writing files to the working directory
+ pub fn checkout_options(&mut self, opts: CheckoutBuilder<'cb>) -> &mut StashApplyOptions<'cb> {
+ self.checkout_options = Some(opts);
+ self
+ }
+
+ /// Optional callback to notify the consumer of application progress.
+ ///
+ /// Return `true` to continue processing, or `false` to
+ /// abort the stash application.
+ pub fn progress_cb<C>(&mut self, callback: C) -> &mut StashApplyOptions<'cb>
+ where
+ C: FnMut(StashApplyProgress) -> bool + 'cb,
+ {
+ self.progress = Some(Box::new(callback) as Box<StashApplyProgressCb<'cb>>);
+ self.raw_opts.progress_cb = Some(stash_apply_progress_cb);
+ self.raw_opts.progress_payload = self as *mut _ as *mut _;
+ self
+ }
+
+ /// Pointer to a raw git_stash_apply_options
+ pub fn raw(&mut self) -> &raw::git_stash_apply_options {
+ unsafe {
+ if let Some(opts) = self.checkout_options.as_mut() {
+ opts.configure(&mut self.raw_opts.checkout_options);
+ }
+ }
+ &self.raw_opts
+ }
+}
+
+pub(crate) struct StashCbData<'a> {
+ pub callback: &'a mut StashCb<'a>,
+}
+
+pub(crate) extern "C" fn stash_cb(
+ index: size_t,
+ message: *const c_char,
+ stash_id: *const raw::git_oid,
+ payload: *mut c_void,
+) -> c_int {
+ panic::wrap(|| unsafe {
+ let data = &mut *(payload as *mut StashCbData<'_>);
+ let res = {
+ let callback = &mut data.callback;
+ callback(
+ index,
+ CStr::from_ptr(message).to_str().unwrap(),
+ &Binding::from_raw(stash_id),
+ )
+ };
+
+ if res {
+ 0
+ } else {
+ 1
+ }
+ })
+ .unwrap_or(1)
+}
+
+fn convert_progress(progress: raw::git_stash_apply_progress_t) -> StashApplyProgress {
+ match progress {
+ raw::GIT_STASH_APPLY_PROGRESS_NONE => StashApplyProgress::None,
+ raw::GIT_STASH_APPLY_PROGRESS_LOADING_STASH => StashApplyProgress::LoadingStash,
+ raw::GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX => StashApplyProgress::AnalyzeIndex,
+ raw::GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED => StashApplyProgress::AnalyzeModified,
+ raw::GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED => StashApplyProgress::AnalyzeUntracked,
+ raw::GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED => StashApplyProgress::CheckoutUntracked,
+ raw::GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED => StashApplyProgress::CheckoutModified,
+ raw::GIT_STASH_APPLY_PROGRESS_DONE => StashApplyProgress::Done,
+
+ _ => StashApplyProgress::None,
+ }
+}
+
+extern "C" fn stash_apply_progress_cb(
+ progress: raw::git_stash_apply_progress_t,
+ payload: *mut c_void,
+) -> c_int {
+ panic::wrap(|| unsafe {
+ let options = &mut *(payload as *mut StashApplyOptions<'_>);
+ let res = {
+ let callback = options.progress.as_mut().unwrap();
+ callback(convert_progress(progress))
+ };
+
+ if res {
+ 0
+ } else {
+ -1
+ }
+ })
+ .unwrap_or(-1)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::stash::{StashApplyOptions, StashSaveOptions};
+ use crate::test::repo_init;
+ use crate::{IndexAddOption, Repository, StashFlags, Status};
+ use std::fs;
+ use std::path::{Path, PathBuf};
+
+ fn make_stash<C>(next: C)
+ where
+ C: FnOnce(&mut Repository),
+ {
+ let (_td, mut repo) = repo_init();
+ let signature = repo.signature().unwrap();
+
+ let p = Path::new(repo.workdir().unwrap()).join("file_b.txt");
+ println!("using path {:?}", p);
+
+ fs::write(&p, "data".as_bytes()).unwrap();
+
+ let rel_p = Path::new("file_b.txt");
+ assert!(repo.status_file(&rel_p).unwrap() == Status::WT_NEW);
+
+ repo.stash_save(&signature, "msg1", Some(StashFlags::INCLUDE_UNTRACKED))
+ .unwrap();
+
+ assert!(repo.status_file(&rel_p).is_err());
+
+ let mut count = 0;
+ repo.stash_foreach(|index, name, _oid| {
+ count += 1;
+ assert!(index == 0);
+ assert!(name == "On main: msg1");
+ true
+ })
+ .unwrap();
+
+ assert!(count == 1);
+ next(&mut repo);
+ }
+
+ fn count_stash(repo: &mut Repository) -> usize {
+ let mut count = 0;
+ repo.stash_foreach(|_, _, _| {
+ count += 1;
+ true
+ })
+ .unwrap();
+ count
+ }
+
+ #[test]
+ fn smoke_stash_save_drop() {
+ make_stash(|repo| {
+ repo.stash_drop(0).unwrap();
+ assert!(count_stash(repo) == 0)
+ })
+ }
+
+ #[test]
+ fn smoke_stash_save_pop() {
+ make_stash(|repo| {
+ repo.stash_pop(0, None).unwrap();
+ assert!(count_stash(repo) == 0)
+ })
+ }
+
+ #[test]
+ fn smoke_stash_save_apply() {
+ make_stash(|repo| {
+ let mut options = StashApplyOptions::new();
+ options.progress_cb(|progress| {
+ println!("{:?}", progress);
+ true
+ });
+
+ repo.stash_apply(0, Some(&mut options)).unwrap();
+ assert!(count_stash(repo) == 1)
+ })
+ }
+
+ #[test]
+ fn test_stash_save2_msg_none() {
+ let (_td, mut repo) = repo_init();
+ let signature = repo.signature().unwrap();
+
+ let p = Path::new(repo.workdir().unwrap()).join("file_b.txt");
+
+ fs::write(&p, "data".as_bytes()).unwrap();
+
+ repo.stash_save2(&signature, None, Some(StashFlags::INCLUDE_UNTRACKED))
+ .unwrap();
+
+ let mut stash_name = String::new();
+ repo.stash_foreach(|index, name, _oid| {
+ assert_eq!(index, 0);
+ stash_name = name.to_string();
+ true
+ })
+ .unwrap();
+
+ assert!(stash_name.starts_with("WIP on main:"));
+ }
+
+ fn create_file(r: &Repository, name: &str, data: &str) -> PathBuf {
+ let p = Path::new(r.workdir().unwrap()).join(name);
+ fs::write(&p, data).unwrap();
+ p
+ }
+
+ #[test]
+ fn test_stash_save_ext() {
+ let (_td, mut repo) = repo_init();
+ let signature = repo.signature().unwrap();
+
+ create_file(&repo, "file_a", "foo");
+ create_file(&repo, "file_b", "foo");
+
+ let mut index = repo.index().unwrap();
+ index
+ .add_all(["*"].iter(), IndexAddOption::DEFAULT, None)
+ .unwrap();
+ index.write().unwrap();
+
+ assert_eq!(repo.statuses(None).unwrap().len(), 2);
+
+ let mut opt = StashSaveOptions::new(signature);
+ opt.pathspec("file_a");
+ repo.stash_save_ext(Some(&mut opt)).unwrap();
+
+ assert_eq!(repo.statuses(None).unwrap().len(), 0);
+
+ repo.stash_pop(0, None).unwrap();
+
+ assert_eq!(repo.statuses(None).unwrap().len(), 1);
+ }
+}