]> git.proxmox.com Git - rustc.git/blame - extra/git2/src/apply.rs
New upstream version 1.73.0+dfsg1
[rustc.git] / extra / git2 / src / apply.rs
CommitLineData
0a29b90c
FG
1//! git_apply support
2//! see original: <https://github.com/libgit2/libgit2/blob/master/include/git2/apply.h>
3
4use crate::{panic, raw, util::Binding, DiffDelta, DiffHunk};
5use libc::c_int;
6use 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)]
11pub 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
20impl 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
40pub 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
46type HunkCB<'a> = dyn FnMut(Option<DiffHunk<'_>>) -> bool + 'a;
47type DeltaCB<'a> = dyn FnMut(Option<DiffDelta<'_>>) -> bool + 'a;
48
49extern "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
69extern "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
89impl<'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)]
152mod 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}