Skip to content

Commit 45ba1b8

Browse files
author
Stephan Dilly
authored
add apply support to repository (#542)
1 parent d2c6f8b commit 45ba1b8

File tree

3 files changed

+230
-1
lines changed

3 files changed

+230
-1
lines changed

src/apply.rs

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
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+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ use std::fmt;
7979
use std::str;
8080
use std::sync::Once;
8181

82+
pub use crate::apply::{ApplyLocation, ApplyOptions};
8283
pub use crate::blame::{Blame, BlameHunk, BlameIter, BlameOptions};
8384
pub use crate::blob::{Blob, BlobWriter};
8485
pub use crate::branch::{Branch, Branches};
@@ -624,6 +625,7 @@ pub mod oid_array;
624625
pub mod string_array;
625626
pub mod transport;
626627

628+
mod apply;
627629
mod blame;
628630
mod blob;
629631
mod branch;

src/repo.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ use crate::{
2020
use crate::{
2121
AnnotatedCommit, MergeAnalysis, MergeOptions, MergePreference, SubmoduleIgnore, SubmoduleStatus,
2222
};
23+
use crate::{ApplyLocation, ApplyOptions, Rebase, RebaseOptions};
2324
use crate::{Blame, BlameOptions, Reference, References, ResetType, Signature, Submodule};
2425
use crate::{Blob, BlobWriter, Branch, BranchType, Branches, Commit, Config, Index, Oid, Tree};
2526
use crate::{Describe, IntoCString, Reflog, RepositoryInitMode, RevparseMode};
2627
use crate::{DescribeOptions, Diff, DiffOptions, Odb, PackBuilder, TreeBuilder};
2728
use crate::{Note, Notes, ObjectType, Revwalk, Status, StatusOptions, Statuses, Tag};
28-
use crate::{Rebase, RebaseOptions};
2929

3030
/// An owned git repository, representing all state associated with the
3131
/// underlying filesystem.
@@ -2562,6 +2562,25 @@ impl Repository {
25622562
Ok(buf)
25632563
}
25642564
}
2565+
2566+
/// Apply a Diff to the given repo, making changes directly in the working directory, the index, or both.
2567+
pub fn apply(
2568+
&self,
2569+
diff: &Diff<'_>,
2570+
location: ApplyLocation,
2571+
options: Option<&mut ApplyOptions<'_>>,
2572+
) -> Result<(), Error> {
2573+
unsafe {
2574+
try_call!(raw::git_apply(
2575+
self.raw,
2576+
diff.raw(),
2577+
location.raw(),
2578+
options.map(|s| s.raw()).unwrap_or(ptr::null())
2579+
));
2580+
2581+
Ok(())
2582+
}
2583+
}
25652584
}
25662585

25672586
impl Binding for Repository {

0 commit comments

Comments
 (0)