Skip to content

Commit d95cc38

Browse files
authored
Merge pull request #930 from extrawurst/support-partial-stashing
Support partial stashing
2 parents 41526ca + dfd4abe commit d95cc38

File tree

4 files changed

+148
-16
lines changed

4 files changed

+148
-16
lines changed

libgit2-sys/lib.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,7 @@ git_enum! {
17311731
GIT_STASH_KEEP_INDEX = 1 << 0,
17321732
GIT_STASH_INCLUDE_UNTRACKED = 1 << 1,
17331733
GIT_STASH_INCLUDE_IGNORED = 1 << 2,
1734+
GIT_STASH_KEEP_ALL = 1 << 3,
17341735
}
17351736
}
17361737

@@ -1754,6 +1755,17 @@ git_enum! {
17541755
}
17551756
}
17561757

1758+
#[repr(C)]
1759+
pub struct git_stash_save_options {
1760+
pub version: c_uint,
1761+
pub flags: u32,
1762+
pub stasher: *const git_signature,
1763+
pub message: *const c_char,
1764+
pub paths: git_strarray,
1765+
}
1766+
1767+
pub const GIT_STASH_SAVE_OPTIONS_VERSION: c_uint = 1;
1768+
17571769
#[repr(C)]
17581770
pub struct git_stash_apply_options {
17591771
pub version: c_uint,
@@ -2534,6 +2546,15 @@ extern "C" {
25342546
flags: c_uint,
25352547
) -> c_int;
25362548

2549+
pub fn git_stash_save_options_init(opts: *mut git_stash_save_options, version: c_uint)
2550+
-> c_int;
2551+
2552+
pub fn git_stash_save_with_opts(
2553+
out: *mut git_oid,
2554+
repo: *mut git_repository,
2555+
options: *const git_stash_save_options,
2556+
) -> c_int;
2557+
25372558
pub fn git_stash_apply_init_options(
25382559
opts: *mut git_stash_apply_options,
25392560
version: c_uint,

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1465,6 +1465,8 @@ bitflags! {
14651465
/// All ignored files are also stashed and then cleaned up from
14661466
/// the working directory
14671467
const INCLUDE_IGNORED = raw::GIT_STASH_INCLUDE_IGNORED as u32;
1468+
/// All changes in the index and working directory are left intact
1469+
const KEEP_ALL = raw::GIT_STASH_KEEP_ALL as u32;
14681470
}
14691471
}
14701472

src/repo.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::diff::{
1212
binary_cb_c, file_cb_c, hunk_cb_c, line_cb_c, BinaryCb, DiffCallbacks, FileCb, HunkCb, LineCb,
1313
};
1414
use crate::oid_array::OidArray;
15-
use crate::stash::{stash_cb, StashApplyOptions, StashCbData};
15+
use crate::stash::{stash_cb, StashApplyOptions, StashCbData, StashSaveOptions};
1616
use crate::string_array::StringArray;
1717
use crate::tagforeach::{tag_foreach_cb, TagForeachCB, TagForeachData};
1818
use crate::util::{self, path_to_repo_path, Binding};
@@ -2844,6 +2844,25 @@ impl Repository {
28442844
}
28452845
}
28462846

2847+
/// Like `stash_save` but with more options like selective statshing via path patterns.
2848+
pub fn stash_save_ext(
2849+
&mut self,
2850+
opts: Option<&mut StashSaveOptions<'_>>,
2851+
) -> Result<Oid, Error> {
2852+
unsafe {
2853+
let mut raw_oid = raw::git_oid {
2854+
id: [0; raw::GIT_OID_RAWSZ],
2855+
};
2856+
let opts = opts.map(|opts| opts.raw());
2857+
try_call!(raw::git_stash_save_with_opts(
2858+
&mut raw_oid,
2859+
self.raw(),
2860+
opts
2861+
));
2862+
Ok(Binding::from_raw(&raw_oid as *const _))
2863+
}
2864+
}
2865+
28472866
/// Apply a single stashed state from the stash list.
28482867
pub fn stash_apply(
28492868
&mut self,

src/stash.rs

Lines changed: 105 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,73 @@
11
use crate::build::CheckoutBuilder;
2-
use crate::util::Binding;
3-
use crate::{panic, raw, Oid, StashApplyProgress};
2+
use crate::util::{self, Binding};
3+
use crate::{panic, raw, IntoCString, Oid, Signature, StashApplyProgress, StashFlags};
44
use libc::{c_char, c_int, c_void, size_t};
5-
use std::ffi::CStr;
5+
use std::ffi::{c_uint, CStr, CString};
66
use std::mem;
77

8+
#[allow(unused)]
9+
/// Stash application options structure
10+
pub struct StashSaveOptions<'a> {
11+
message: Option<CString>,
12+
flags: Option<StashFlags>,
13+
stasher: Signature<'a>,
14+
pathspec: Vec<CString>,
15+
pathspec_ptrs: Vec<*const c_char>,
16+
raw_opts: raw::git_stash_save_options,
17+
}
18+
19+
impl<'a> StashSaveOptions<'a> {
20+
/// Creates a default
21+
pub fn new(stasher: Signature<'a>) -> Self {
22+
let mut opts = Self {
23+
message: None,
24+
flags: None,
25+
stasher,
26+
pathspec: Vec::new(),
27+
pathspec_ptrs: Vec::new(),
28+
raw_opts: unsafe { mem::zeroed() },
29+
};
30+
assert_eq!(
31+
unsafe {
32+
raw::git_stash_save_options_init(
33+
&mut opts.raw_opts,
34+
raw::GIT_STASH_SAVE_OPTIONS_VERSION,
35+
)
36+
},
37+
0
38+
);
39+
opts
40+
}
41+
42+
/// Customize optional `flags` field
43+
pub fn flags(&mut self, flags: Option<StashFlags>) -> &mut Self {
44+
self.flags = flags;
45+
self
46+
}
47+
48+
/// Add to the array of paths patterns to build the stash.
49+
pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut Self {
50+
let s = util::cstring_to_repo_path(pathspec).unwrap();
51+
self.pathspec_ptrs.push(s.as_ptr());
52+
self.pathspec.push(s);
53+
self
54+
}
55+
56+
/// Acquire a pointer to the underlying raw options.
57+
///
58+
/// This function is unsafe as the pointer is only valid so long as this
59+
/// structure is not moved, modified, or used elsewhere.
60+
pub unsafe fn raw(&mut self) -> *const raw::git_stash_save_options {
61+
self.raw_opts.flags = self.flags.unwrap_or_else(StashFlags::empty).bits as c_uint;
62+
self.raw_opts.message = crate::call::convert(&self.message);
63+
self.raw_opts.paths.count = self.pathspec_ptrs.len() as size_t;
64+
self.raw_opts.paths.strings = self.pathspec_ptrs.as_ptr() as *mut _;
65+
self.raw_opts.stasher = self.stasher.raw();
66+
67+
&self.raw_opts as *const _
68+
}
69+
}
70+
871
/// Stash application progress notification function.
972
///
1073
/// Return `true` to continue processing, or `false` to
@@ -151,12 +214,11 @@ extern "C" fn stash_apply_progress_cb(
151214

152215
#[cfg(test)]
153216
mod tests {
154-
use crate::stash::StashApplyOptions;
217+
use crate::stash::{StashApplyOptions, StashSaveOptions};
155218
use crate::test::repo_init;
156-
use crate::{Repository, StashFlags, Status};
219+
use crate::{IndexAddOption, Repository, StashFlags, Status};
157220
use std::fs;
158-
use std::io::Write;
159-
use std::path::Path;
221+
use std::path::{Path, PathBuf};
160222

161223
fn make_stash<C>(next: C)
162224
where
@@ -167,10 +229,8 @@ mod tests {
167229

168230
let p = Path::new(repo.workdir().unwrap()).join("file_b.txt");
169231
println!("using path {:?}", p);
170-
fs::File::create(&p)
171-
.unwrap()
172-
.write("data".as_bytes())
173-
.unwrap();
232+
233+
fs::write(&p, "data".as_bytes()).unwrap();
174234

175235
let rel_p = Path::new("file_b.txt");
176236
assert!(repo.status_file(&rel_p).unwrap() == Status::WT_NEW);
@@ -240,10 +300,7 @@ mod tests {
240300

241301
let p = Path::new(repo.workdir().unwrap()).join("file_b.txt");
242302

243-
fs::File::create(&p)
244-
.unwrap()
245-
.write("data".as_bytes())
246-
.unwrap();
303+
fs::write(&p, "data".as_bytes()).unwrap();
247304

248305
repo.stash_save2(&signature, None, Some(StashFlags::INCLUDE_UNTRACKED))
249306
.unwrap();
@@ -258,4 +315,37 @@ mod tests {
258315

259316
assert!(stash_name.starts_with("WIP on main:"));
260317
}
318+
319+
fn create_file(r: &Repository, name: &str, data: &str) -> PathBuf {
320+
let p = Path::new(r.workdir().unwrap()).join(name);
321+
fs::write(&p, data).unwrap();
322+
p
323+
}
324+
325+
#[test]
326+
fn test_stash_save_ext() {
327+
let (_td, mut repo) = repo_init();
328+
let signature = repo.signature().unwrap();
329+
330+
create_file(&repo, "file_a", "foo");
331+
create_file(&repo, "file_b", "foo");
332+
333+
let mut index = repo.index().unwrap();
334+
index
335+
.add_all(["*"].iter(), IndexAddOption::DEFAULT, None)
336+
.unwrap();
337+
index.write().unwrap();
338+
339+
assert_eq!(repo.statuses(None).unwrap().len(), 2);
340+
341+
let mut opt = StashSaveOptions::new(signature);
342+
opt.pathspec("file_a");
343+
repo.stash_save_ext(Some(&mut opt)).unwrap();
344+
345+
assert_eq!(repo.statuses(None).unwrap().len(), 0);
346+
347+
repo.stash_pop(0, None).unwrap();
348+
349+
assert_eq!(repo.statuses(None).unwrap().len(), 1);
350+
}
261351
}

0 commit comments

Comments
 (0)