]>
Commit | Line | Data |
---|---|---|
0a29b90c FG |
1 | //! git_apply support |
2 | //! see original: <https://github.com/libgit2/libgit2/blob/master/include/git2/apply.h> | |
3 | ||
4 | use crate::{panic, raw, util::Binding, DiffDelta, DiffHunk}; | |
5 | use libc::c_int; | |
6 | use std::{ffi::c_void, mem}; | |
7 | ||
8 | /// Possible application locations for git_apply | |
9 | /// see <https://libgit2.org/libgit2/#HEAD/type/git_apply_options> | |
10 | #[derive(Copy, Clone, Debug)] | |
11 | pub enum ApplyLocation { | |
12 | /// Apply the patch to the workdir | |
13 | WorkDir, | |
14 | /// Apply the patch to the index | |
15 | Index, | |
16 | /// Apply the patch to both the working directory and the index | |
17 | Both, | |
18 | } | |
19 | ||
20 | impl Binding for ApplyLocation { | |
21 | type Raw = raw::git_apply_location_t; | |
22 | unsafe fn from_raw(raw: raw::git_apply_location_t) -> Self { | |
23 | match raw { | |
24 | raw::GIT_APPLY_LOCATION_WORKDIR => Self::WorkDir, | |
25 | raw::GIT_APPLY_LOCATION_INDEX => Self::Index, | |
26 | raw::GIT_APPLY_LOCATION_BOTH => Self::Both, | |
27 | _ => panic!("Unknown git diff binary kind"), | |
28 | } | |
29 | } | |
30 | fn raw(&self) -> raw::git_apply_location_t { | |
31 | match *self { | |
32 | Self::WorkDir => raw::GIT_APPLY_LOCATION_WORKDIR, | |
33 | Self::Index => raw::GIT_APPLY_LOCATION_INDEX, | |
34 | Self::Both => raw::GIT_APPLY_LOCATION_BOTH, | |
35 | } | |
36 | } | |
37 | } | |
38 | ||
39 | /// Options to specify when applying a diff | |
40 | pub struct ApplyOptions<'cb> { | |
41 | raw: raw::git_apply_options, | |
42 | hunk_cb: Option<Box<HunkCB<'cb>>>, | |
43 | delta_cb: Option<Box<DeltaCB<'cb>>>, | |
44 | } | |
45 | ||
46 | type HunkCB<'a> = dyn FnMut(Option<DiffHunk<'_>>) -> bool + 'a; | |
47 | type DeltaCB<'a> = dyn FnMut(Option<DiffDelta<'_>>) -> bool + 'a; | |
48 | ||
49 | extern "C" fn delta_cb_c(delta: *const raw::git_diff_delta, data: *mut c_void) -> c_int { | |
50 | panic::wrap(|| unsafe { | |
51 | let delta = Binding::from_raw_opt(delta as *mut _); | |
52 | ||
53 | let payload = &mut *(data as *mut ApplyOptions<'_>); | |
54 | let callback = match payload.delta_cb { | |
55 | Some(ref mut c) => c, | |
56 | None => return -1, | |
57 | }; | |
58 | ||
59 | let apply = callback(delta); | |
60 | if apply { | |
61 | 0 | |
62 | } else { | |
63 | 1 | |
64 | } | |
65 | }) | |
66 | .unwrap_or(-1) | |
67 | } | |
68 | ||
69 | extern "C" fn hunk_cb_c(hunk: *const raw::git_diff_hunk, data: *mut c_void) -> c_int { | |
70 | panic::wrap(|| unsafe { | |
71 | let hunk = Binding::from_raw_opt(hunk); | |
72 | ||
73 | let payload = &mut *(data as *mut ApplyOptions<'_>); | |
74 | let callback = match payload.hunk_cb { | |
75 | Some(ref mut c) => c, | |
76 | None => return -1, | |
77 | }; | |
78 | ||
79 | let apply = callback(hunk); | |
80 | if apply { | |
81 | 0 | |
82 | } else { | |
83 | 1 | |
84 | } | |
85 | }) | |
86 | .unwrap_or(-1) | |
87 | } | |
88 | ||
89 | impl<'cb> ApplyOptions<'cb> { | |
90 | /// Creates a new set of empty options (zeroed). | |
91 | pub fn new() -> Self { | |
92 | let mut opts = Self { | |
93 | raw: unsafe { mem::zeroed() }, | |
94 | hunk_cb: None, | |
95 | delta_cb: None, | |
96 | }; | |
97 | assert_eq!( | |
98 | unsafe { raw::git_apply_options_init(&mut opts.raw, raw::GIT_APPLY_OPTIONS_VERSION) }, | |
99 | 0 | |
100 | ); | |
101 | opts | |
102 | } | |
103 | ||
104 | fn flag(&mut self, opt: raw::git_apply_flags_t, val: bool) -> &mut Self { | |
105 | let opt = opt as u32; | |
106 | if val { | |
107 | self.raw.flags |= opt; | |
108 | } else { | |
109 | self.raw.flags &= !opt; | |
110 | } | |
111 | self | |
112 | } | |
113 | ||
114 | /// Don't actually make changes, just test that the patch applies. | |
115 | pub fn check(&mut self, check: bool) -> &mut Self { | |
116 | self.flag(raw::GIT_APPLY_CHECK, check) | |
117 | } | |
118 | ||
119 | /// When applying a patch, callback that will be made per hunk. | |
120 | pub fn hunk_callback<F>(&mut self, cb: F) -> &mut Self | |
121 | where | |
122 | F: FnMut(Option<DiffHunk<'_>>) -> bool + 'cb, | |
123 | { | |
124 | self.hunk_cb = Some(Box::new(cb) as Box<HunkCB<'cb>>); | |
125 | ||
126 | self.raw.hunk_cb = Some(hunk_cb_c); | |
127 | self.raw.payload = self as *mut _ as *mut _; | |
128 | ||
129 | self | |
130 | } | |
131 | ||
132 | /// When applying a patch, callback that will be made per delta (file). | |
133 | pub fn delta_callback<F>(&mut self, cb: F) -> &mut Self | |
134 | where | |
135 | F: FnMut(Option<DiffDelta<'_>>) -> bool + 'cb, | |
136 | { | |
137 | self.delta_cb = Some(Box::new(cb) as Box<DeltaCB<'cb>>); | |
138 | ||
139 | self.raw.delta_cb = Some(delta_cb_c); | |
140 | self.raw.payload = self as *mut _ as *mut _; | |
141 | ||
142 | self | |
143 | } | |
144 | ||
145 | /// Pointer to a raw git_stash_apply_options | |
146 | pub unsafe fn raw(&mut self) -> *const raw::git_apply_options { | |
147 | &self.raw as *const _ | |
148 | } | |
149 | } | |
150 | ||
151 | #[cfg(test)] | |
152 | mod tests { | |
153 | use super::*; | |
154 | use std::{fs::File, io::Write, path::Path}; | |
155 | ||
156 | #[test] | |
157 | fn smoke_test() { | |
158 | let (_td, repo) = crate::test::repo_init(); | |
159 | let diff = t!(repo.diff_tree_to_workdir(None, None)); | |
160 | let mut count_hunks = 0; | |
161 | let mut count_delta = 0; | |
162 | { | |
163 | let mut opts = ApplyOptions::new(); | |
164 | opts.hunk_callback(|_hunk| { | |
165 | count_hunks += 1; | |
166 | true | |
167 | }); | |
168 | opts.delta_callback(|_delta| { | |
169 | count_delta += 1; | |
170 | true | |
171 | }); | |
172 | t!(repo.apply(&diff, ApplyLocation::Both, Some(&mut opts))); | |
173 | } | |
174 | assert_eq!(count_hunks, 0); | |
175 | assert_eq!(count_delta, 0); | |
176 | } | |
177 | ||
178 | #[test] | |
179 | fn apply_hunks_and_delta() { | |
180 | let file_path = Path::new("foo.txt"); | |
181 | let (td, repo) = crate::test::repo_init(); | |
182 | // create new file | |
183 | t!(t!(File::create(&td.path().join(file_path))).write_all(b"bar")); | |
184 | // stage the new file | |
185 | t!(t!(repo.index()).add_path(file_path)); | |
186 | // now change workdir version | |
187 | t!(t!(File::create(&td.path().join(file_path))).write_all(b"foo\nbar")); | |
188 | ||
189 | let diff = t!(repo.diff_index_to_workdir(None, None)); | |
190 | assert_eq!(diff.deltas().len(), 1); | |
191 | let mut count_hunks = 0; | |
192 | let mut count_delta = 0; | |
193 | { | |
194 | let mut opts = ApplyOptions::new(); | |
195 | opts.hunk_callback(|_hunk| { | |
196 | count_hunks += 1; | |
197 | true | |
198 | }); | |
199 | opts.delta_callback(|_delta| { | |
200 | count_delta += 1; | |
201 | true | |
202 | }); | |
203 | t!(repo.apply(&diff, ApplyLocation::Index, Some(&mut opts))); | |
204 | } | |
205 | assert_eq!(count_delta, 1); | |
206 | assert_eq!(count_hunks, 1); | |
207 | } | |
208 | } |