1use libc::{c_char, c_uint, c_ushort, size_t};
2use std::ffi::CString;
3use std::marker;
4use std::mem;
5use std::ptr;
6use std::str;
7
8use crate::call::Convert;
9use crate::util::Binding;
10use crate::{raw, Commit, Error, FileFavor, FileMode, IntoCString, Oid};
11
12pub struct AnnotatedCommit<'repo> {
18 raw: *mut raw::git_annotated_commit,
19 _marker: marker::PhantomData<Commit<'repo>>,
20}
21
22pub struct MergeOptions {
24 raw: raw::git_merge_options,
25}
26
27pub struct MergeFileOptions {
29 ancestor_label: Option<CString>,
30 our_label: Option<CString>,
31 their_label: Option<CString>,
32 raw: raw::git_merge_file_options,
33}
34
35pub struct MergeFileResult {
37 raw: raw::git_merge_file_result,
38}
39
40impl<'repo> AnnotatedCommit<'repo> {
41 pub fn id(&self) -> Oid {
43 unsafe { Binding::from_raw(raw::git_annotated_commit_id(self.raw)) }
44 }
45
46 pub fn refname(&self) -> Result<&str, Error> {
48 str::from_utf8(self.refname_bytes()).map_err(|e| e.into())
49 }
50
51 pub fn refname_bytes(&self) -> &[u8] {
53 unsafe { crate::opt_bytes(self, raw::git_annotated_commit_ref(&*self.raw)).unwrap() }
54 }
55}
56
57impl Default for MergeOptions {
58 fn default() -> Self {
59 Self::new()
60 }
61}
62
63impl MergeOptions {
64 pub fn new() -> MergeOptions {
66 let mut opts = MergeOptions {
67 raw: unsafe { mem::zeroed() },
68 };
69 assert_eq!(unsafe { raw::git_merge_init_options(&mut opts.raw, 1) }, 0);
70 opts
71 }
72
73 fn flag(&mut self, opt: u32, val: bool) -> &mut MergeOptions {
74 if val {
75 self.raw.flags |= opt;
76 } else {
77 self.raw.flags &= !opt;
78 }
79 self
80 }
81
82 pub fn find_renames(&mut self, find: bool) -> &mut MergeOptions {
84 self.flag(raw::GIT_MERGE_FIND_RENAMES as u32, find)
85 }
86
87 pub fn fail_on_conflict(&mut self, fail: bool) -> &mut MergeOptions {
90 self.flag(raw::GIT_MERGE_FAIL_ON_CONFLICT as u32, fail)
91 }
92
93 pub fn skip_reuc(&mut self, skip: bool) -> &mut MergeOptions {
95 self.flag(raw::GIT_MERGE_SKIP_REUC as u32, skip)
96 }
97
98 pub fn no_recursive(&mut self, disable: bool) -> &mut MergeOptions {
102 self.flag(raw::GIT_MERGE_NO_RECURSIVE as u32, disable)
103 }
104
105 pub fn rename_threshold(&mut self, thresh: u32) -> &mut MergeOptions {
107 self.raw.rename_threshold = thresh;
108 self
109 }
110
111 pub fn target_limit(&mut self, limit: u32) -> &mut MergeOptions {
116 self.raw.target_limit = limit as c_uint;
117 self
118 }
119
120 pub fn recursion_limit(&mut self, limit: u32) -> &mut MergeOptions {
125 self.raw.recursion_limit = limit as c_uint;
126 self
127 }
128
129 pub fn file_favor(&mut self, favor: FileFavor) -> &mut MergeOptions {
131 self.raw.file_favor = favor.convert();
132 self
133 }
134
135 fn file_flag(&mut self, opt: u32, val: bool) -> &mut MergeOptions {
136 if val {
137 self.raw.file_flags |= opt;
138 } else {
139 self.raw.file_flags &= !opt;
140 }
141 self
142 }
143
144 pub fn standard_style(&mut self, standard: bool) -> &mut MergeOptions {
146 self.file_flag(raw::GIT_MERGE_FILE_STYLE_MERGE as u32, standard)
147 }
148
149 pub fn diff3_style(&mut self, diff3: bool) -> &mut MergeOptions {
151 self.file_flag(raw::GIT_MERGE_FILE_STYLE_DIFF3 as u32, diff3)
152 }
153
154 pub fn simplify_alnum(&mut self, simplify: bool) -> &mut MergeOptions {
156 self.file_flag(raw::GIT_MERGE_FILE_SIMPLIFY_ALNUM as u32, simplify)
157 }
158
159 pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut MergeOptions {
161 self.file_flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE as u32, ignore)
162 }
163
164 pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut MergeOptions {
166 self.file_flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE as u32, ignore)
167 }
168
169 pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut MergeOptions {
171 self.file_flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL as u32, ignore)
172 }
173
174 pub fn patience(&mut self, patience: bool) -> &mut MergeOptions {
176 self.file_flag(raw::GIT_MERGE_FILE_DIFF_PATIENCE as u32, patience)
177 }
178
179 pub fn minimal(&mut self, minimal: bool) -> &mut MergeOptions {
181 self.file_flag(raw::GIT_MERGE_FILE_DIFF_MINIMAL as u32, minimal)
182 }
183
184 pub unsafe fn raw(&self) -> *const raw::git_merge_options {
186 &self.raw as *const _
187 }
188}
189
190impl<'repo> Binding for AnnotatedCommit<'repo> {
191 type Raw = *mut raw::git_annotated_commit;
192 unsafe fn from_raw(raw: *mut raw::git_annotated_commit) -> AnnotatedCommit<'repo> {
193 AnnotatedCommit {
194 raw,
195 _marker: marker::PhantomData,
196 }
197 }
198 fn raw(&self) -> *mut raw::git_annotated_commit {
199 self.raw
200 }
201}
202
203impl<'repo> Drop for AnnotatedCommit<'repo> {
204 fn drop(&mut self) {
205 unsafe { raw::git_annotated_commit_free(self.raw) }
206 }
207}
208
209impl Default for MergeFileOptions {
210 fn default() -> Self {
211 Self::new()
212 }
213}
214
215impl MergeFileOptions {
216 pub fn new() -> MergeFileOptions {
218 let mut opts = MergeFileOptions {
219 ancestor_label: None,
220 our_label: None,
221 their_label: None,
222 raw: unsafe { mem::zeroed() },
223 };
224 assert_eq!(
225 unsafe { raw::git_merge_file_options_init(&mut opts.raw, 1) },
226 0
227 );
228 opts
229 }
230
231 pub fn ancestor_label<T: IntoCString>(&mut self, t: T) -> &mut MergeFileOptions {
234 self.ancestor_label = Some(t.into_c_string().unwrap());
235
236 self.raw.ancestor_label = self
237 .ancestor_label
238 .as_ref()
239 .map(|s| s.as_ptr())
240 .unwrap_or(ptr::null());
241
242 self
243 }
244
245 pub fn our_label<T: IntoCString>(&mut self, t: T) -> &mut MergeFileOptions {
248 self.our_label = Some(t.into_c_string().unwrap());
249
250 self.raw.our_label = self
251 .our_label
252 .as_ref()
253 .map(|s| s.as_ptr())
254 .unwrap_or(ptr::null());
255
256 self
257 }
258
259 pub fn their_label<T: IntoCString>(&mut self, t: T) -> &mut MergeFileOptions {
262 self.their_label = Some(t.into_c_string().unwrap());
263
264 self.raw.their_label = self
265 .their_label
266 .as_ref()
267 .map(|s| s.as_ptr())
268 .unwrap_or(ptr::null());
269
270 self
271 }
272
273 pub fn favor(&mut self, favor: FileFavor) -> &mut MergeFileOptions {
275 self.raw.favor = favor.convert();
276 self
277 }
278
279 fn flag(&mut self, opt: raw::git_merge_file_flag_t, val: bool) -> &mut MergeFileOptions {
280 if val {
281 self.raw.flags |= opt as u32;
282 } else {
283 self.raw.flags &= !opt as u32;
284 }
285 self
286 }
287
288 pub fn style_standard(&mut self, standard: bool) -> &mut MergeFileOptions {
290 self.flag(raw::GIT_MERGE_FILE_STYLE_MERGE, standard)
291 }
292
293 pub fn style_diff3(&mut self, diff3: bool) -> &mut MergeFileOptions {
295 self.flag(raw::GIT_MERGE_FILE_STYLE_DIFF3, diff3)
296 }
297
298 pub fn simplify_alnum(&mut self, simplify: bool) -> &mut MergeFileOptions {
300 self.flag(raw::GIT_MERGE_FILE_SIMPLIFY_ALNUM, simplify)
301 }
302
303 pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut MergeFileOptions {
305 self.flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE, ignore)
306 }
307
308 pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut MergeFileOptions {
310 self.flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE, ignore)
311 }
312
313 pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut MergeFileOptions {
315 self.flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL, ignore)
316 }
317
318 pub fn patience(&mut self, patience: bool) -> &mut MergeFileOptions {
320 self.flag(raw::GIT_MERGE_FILE_DIFF_PATIENCE, patience)
321 }
322
323 pub fn minimal(&mut self, minimal: bool) -> &mut MergeFileOptions {
325 self.flag(raw::GIT_MERGE_FILE_DIFF_MINIMAL, minimal)
326 }
327
328 pub fn style_zdiff3(&mut self, zdiff3: bool) -> &mut MergeFileOptions {
330 self.flag(raw::GIT_MERGE_FILE_STYLE_ZDIFF3, zdiff3)
331 }
332
333 pub fn accept_conflicts(&mut self, accept: bool) -> &mut MergeFileOptions {
335 self.flag(raw::GIT_MERGE_FILE_ACCEPT_CONFLICTS, accept)
336 }
337
338 pub fn marker_size(&mut self, size: u16) -> &mut MergeFileOptions {
340 self.raw.marker_size = size as c_ushort;
341 self
342 }
343
344 pub(crate) unsafe fn raw(&mut self) -> *const raw::git_merge_file_options {
349 &self.raw
350 }
351}
352
353impl MergeFileResult {
354 pub fn is_automergeable(&self) -> bool {
357 self.raw.automergeable > 0
358 }
359
360 pub fn path(&self) -> Result<Option<&str>, Error> {
364 match self.path_bytes() {
365 Some(pb) => str::from_utf8(pb).map(|s| Some(s)).map_err(|e| e.into()),
366 None => Ok(None),
367 }
368 }
369
370 pub fn path_bytes(&self) -> Option<&[u8]> {
372 unsafe { crate::opt_bytes(self, self.raw.path) }
373 }
374
375 pub fn mode(&self) -> u32 {
377 self.raw.mode as u32
378 }
379
380 pub fn content(&self) -> &[u8] {
382 unsafe { std::slice::from_raw_parts(self.raw.ptr as *const u8, self.raw.len as usize) }
383 }
384}
385
386impl Binding for MergeFileResult {
387 type Raw = raw::git_merge_file_result;
388 unsafe fn from_raw(raw: raw::git_merge_file_result) -> MergeFileResult {
389 MergeFileResult { raw }
390 }
391 fn raw(&self) -> raw::git_merge_file_result {
392 unimplemented!()
393 }
394}
395
396impl Drop for MergeFileResult {
397 fn drop(&mut self) {
398 unsafe { raw::git_merge_file_result_free(&mut self.raw) }
399 }
400}
401
402impl std::fmt::Debug for MergeFileResult {
403 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
404 let mut ds = f.debug_struct("MergeFileResult");
405 if let Ok(Some(path)) = &self.path() {
406 ds.field("path", path);
407 }
408 ds.field("automergeable", &self.is_automergeable());
409 ds.field("mode", &self.mode());
410 ds.finish()
411 }
412}
413
414pub struct MergeFileInput<'a> {
416 raw: raw::git_merge_file_input,
417 path: Option<CString>,
418 content: Option<&'a [u8]>,
419}
420
421impl Default for MergeFileInput<'_> {
422 fn default() -> Self {
423 Self::new()
424 }
425}
426
427impl Binding for MergeFileInput<'_> {
428 type Raw = *const raw::git_merge_file_input;
429
430 unsafe fn from_raw(_raw: *const raw::git_merge_file_input) -> MergeFileInput<'static> {
431 panic!("unimplemened")
432 }
433
434 fn raw(&self) -> *const raw::git_merge_file_input {
435 &self.raw as *const _
436 }
437}
438
439impl<'a> MergeFileInput<'a> {
440 pub fn new() -> MergeFileInput<'a> {
442 let mut input = MergeFileInput {
443 raw: unsafe { mem::zeroed() },
444 path: None,
445 content: None,
446 };
447 assert_eq!(
448 unsafe { raw::git_merge_file_input_init(&mut input.raw, 1) },
449 0
450 );
451 input
452 }
453
454 pub fn content(&mut self, content: &'a [u8]) -> &mut MergeFileInput<'a> {
456 self.content = Some(content);
457
458 self.raw.ptr = content.as_ptr() as *const c_char;
459 self.raw.size = content.len() as size_t;
460
461 self
462 }
463
464 pub fn path<T: IntoCString>(&mut self, t: T) -> &mut MergeFileInput<'a> {
466 self.path = Some(t.into_c_string().unwrap());
467
468 self.raw.path = self
469 .path
470 .as_ref()
471 .map(|s| s.as_ptr())
472 .unwrap_or(ptr::null());
473
474 self
475 }
476
477 pub fn mode(&mut self, mode: Option<FileMode>) -> &mut MergeFileInput<'a> {
479 self.raw.mode = mode.map_or(0, u32::from);
480 self
481 }
482}
483
484pub fn merge_file(
486 ancestor: &MergeFileInput<'_>,
487 ours: &MergeFileInput<'_>,
488 theirs: &MergeFileInput<'_>,
489 opts: Option<&mut MergeFileOptions>,
490) -> Result<MergeFileResult, Error> {
491 unsafe {
492 let ancestor = ancestor.raw();
493 let ours = ours.raw();
494 let theirs = theirs.raw();
495
496 let mut ret = mem::zeroed();
497 try_call!(raw::git_merge_file(
498 &mut ret,
499 ancestor,
500 ours,
501 theirs,
502 opts.map(|o| o.raw()).unwrap_or(ptr::null())
503 ));
504 Ok(Binding::from_raw(ret))
505 }
506}
507
508#[cfg(test)]
509mod tests {
510 use crate::{MergeFileInput, MergeFileOptions};
511
512 use std::path::Path;
513
514 #[test]
515 fn smoke_merge_file() {
516 let file_path = Path::new("file");
517 let base = {
518 let mut input = MergeFileInput::new();
519 input.content(b"base").path(file_path);
520 input
521 };
522
523 let ours = {
524 let mut input = MergeFileInput::new();
525 input.content(b"foo").path(file_path);
526 input
527 };
528
529 let theirs = {
530 let mut input = MergeFileInput::new();
531 input.content(b"bar").path(file_path);
532 input
533 };
534
535 let mut opts = MergeFileOptions::new();
536 opts.ancestor_label("ancestor");
537 opts.our_label("ours");
538 opts.their_label("theirs");
539 opts.style_diff3(true);
540 let merge_file_result = crate::merge_file(&base, &ours, &theirs, Some(&mut opts)).unwrap();
541
542 assert!(!merge_file_result.is_automergeable());
543 assert_eq!(merge_file_result.path(), Ok(Some("file")));
544 assert_eq!(
545 String::from_utf8_lossy(merge_file_result.content()).to_string(),
546 r"<<<<<<< ours
547foo
548||||||| ancestor
549base
550=======
551bar
552>>>>>>> theirs
553",
554 );
555 }
556}