From 57766bc79e20c144b2a316f4a2e27619267e2ffb Mon Sep 17 00:00:00 2001 From: K Date: Sun, 22 Nov 2020 15:49:27 +0800 Subject: [PATCH 1/5] Expose git_merge_file function from libgit2 to repo.rs of git2-rs --- libgit2-sys/lib.rs | 77 ++++++++++++ src/lib.rs | 32 ++++- src/merge.rs | 296 ++++++++++++++++++++++++++++++++++++++++++++- src/repo.rs | 228 +++++++++++++++++++++++++++++++++- 4 files changed, 629 insertions(+), 4 deletions(-) diff --git a/libgit2-sys/lib.rs b/libgit2-sys/lib.rs index 26f1cd0873..ea19d0cf96 100644 --- a/libgit2-sys/lib.rs +++ b/libgit2-sys/lib.rs @@ -8,6 +8,7 @@ use libc::{c_char, c_int, c_uchar, c_uint, c_void, size_t}; #[cfg(feature = "ssh")] use libssh2_sys as libssh2; use std::ffi::CStr; +use std::os::raw::c_ushort; pub const GIT_OID_RAWSZ: usize = 20; pub const GIT_OID_HEXSZ: usize = GIT_OID_RAWSZ * 2; @@ -3854,6 +3855,82 @@ extern "C" { ) -> c_int; } +#[repr(C)] +pub struct git_merge_file_options { + pub version: c_uint, + + /// Label for the ancestor file side of the conflict which will be prepended + /// to labels in diff3-format merge files. + pub ancestor_label: *const c_char, + + /// Label for our file side of the conflict which will be prepended + /// to labels in merge files. + pub our_label: *const c_char, + + /// Label for their file side of the conflict which will be prepended + /// to labels in merge files. + pub their_label: *const c_char, + + /// The file to favor in region conflicts. + pub favor: git_merge_file_favor_t, + + /// see `git_merge_file_flag_t` + pub flags: c_uint, + pub marker_size: c_ushort, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct git_merge_file_input { + pub version: c_uint, + /// Pointer to the contents of the file. + pub ptr: *const c_char, + /// Size of the contents pointed to in `ptr`. + pub size: size_t, + /// File name of the conflicted file, or `NULL` to not merge the path. + pub path: *const c_char, + /// File mode of the conflicted file, or `0` to not merge the mode. + pub mode: c_uint, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct git_merge_file_result { + /// True if the output was automerged, false if the output contains + /// conflict markers. + pub automergeable: c_uint, + + /// The path that the resultant merge file should use, or NULL if a + /// filename conflict would occur. + pub path: *const c_char, + + /// The mode that the resultant merge file should use. + pub mode: c_uint, + + /// The contents of the merge. + pub ptr: *const c_char, + + /// The length of the merge contents. + pub len: size_t, +} + +extern "C" { + pub fn git_merge_file_options_init(opts: *mut git_merge_file_options, version: c_uint) + -> c_int; + pub fn git_merge_file_input_init(opts: *mut git_merge_file_input, version: c_uint) -> c_int; + + pub fn git_merge_file( + out: *mut git_merge_file_result, + ancestor: *const git_merge_file_input, + ours: *const git_merge_file_input, + theirs: *const git_merge_file_input, + opts: *const git_merge_file_options, + ) -> c_int; + + // Not used? + pub fn git_merge_file_result_free(result: *mut git_merge_file_result); +} + pub fn init() { use std::sync::Once; diff --git a/src/lib.rs b/src/lib.rs index cb2e871cca..eeb484eb3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,7 +98,9 @@ pub use crate::index::{ }; pub use crate::indexer::{IndexerProgress, Progress}; pub use crate::mempack::Mempack; -pub use crate::merge::{AnnotatedCommit, MergeOptions}; +pub use crate::merge::{ + AnnotatedCommit, MergeFileInput, MergeFileOptions, MergeFileResult, MergeOptions, +}; pub use crate::message::{message_prettify, DEFAULT_COMMENT_CHAR}; pub use crate::note::{Note, Notes}; pub use crate::object::Object; @@ -1049,6 +1051,34 @@ pub enum FileMode { Commit, } +impl From for FileMode { + fn from(mode: u32) -> Self { + match mode { + raw::GIT_FILEMODE_UNREADABLE => FileMode::Unreadable, + raw::GIT_FILEMODE_TREE => FileMode::Tree, + raw::GIT_FILEMODE_BLOB => FileMode::Blob, + raw::GIT_FILEMODE_BLOB_EXECUTABLE => FileMode::BlobExecutable, + raw::GIT_FILEMODE_LINK => FileMode::Link, + raw::GIT_FILEMODE_COMMIT => FileMode::Commit, + mode => panic!("unknown file mode: {}", mode), + } + } +} + +impl Into for FileMode { + fn into(self) -> u32 { + let ret = match self { + FileMode::Unreadable => raw::GIT_FILEMODE_UNREADABLE, + FileMode::Tree => raw::GIT_FILEMODE_TREE, + FileMode::Blob => raw::GIT_FILEMODE_BLOB, + FileMode::BlobExecutable => raw::GIT_FILEMODE_BLOB_EXECUTABLE, + FileMode::Link => raw::GIT_FILEMODE_LINK, + FileMode::Commit => raw::GIT_FILEMODE_COMMIT, + }; + ret as u32 + } +} + bitflags! { /// Return codes for submodule status. /// diff --git a/src/merge.rs b/src/merge.rs index 4c2749bf9c..1a211c5dfd 100644 --- a/src/merge.rs +++ b/src/merge.rs @@ -5,7 +5,9 @@ use std::str; use crate::call::Convert; use crate::util::Binding; -use crate::{raw, Commit, FileFavor, Oid}; +use crate::{raw, Commit, FileFavor, FileMode, IndexEntry, IntoCString, Oid, Repository}; +use core::{ptr, slice}; +use std::ffi::{CStr, CString}; /// A structure to represent an annotated commit, the input to merge and rebase. /// @@ -192,3 +194,295 @@ impl<'repo> Drop for AnnotatedCommit<'repo> { unsafe { raw::git_annotated_commit_free(self.raw) } } } + +/// Options for merging files +pub struct MergeFileOptions { + /// Label for the ancestor file side of the conflict which will be prepended + /// to labels in diff3-format merge files. + ancestor_label: Option, + + /// Label for our file side of the conflict which will be prepended + /// to labels in merge files. + our_label: Option, + + /// Label for their file side of the conflict which will be prepended + /// to labels in merge files. + their_label: Option, + + // raw data + raw: raw::git_merge_file_options, +} + +impl Default for MergeFileOptions { + fn default() -> Self { + Self::new() + } +} + +impl MergeFileOptions { + /// Creates a default set of merge options. + pub fn new() -> MergeFileOptions { + let mut opts = MergeFileOptions { + ancestor_label: None, + our_label: None, + their_label: None, + raw: unsafe { mem::zeroed() }, + }; + assert_eq!( + unsafe { raw::git_merge_file_options_init(&mut opts.raw, 1) }, + 0 + ); + opts + } + + /// Specify ancestor label, default is "ancestor" + pub fn ancestor_label(&mut self, t: T) -> &mut MergeFileOptions { + self.ancestor_label = Some(t.into_c_string().unwrap()); + + self.raw.ancestor_label = self + .ancestor_label + .as_ref() + .map(|s| s.as_ptr()) + .unwrap_or(ptr::null()); + + self + } + + /// Specify ancestor label, default is "ours" + pub fn our_label(&mut self, t: T) -> &mut MergeFileOptions { + self.our_label = Some(t.into_c_string().unwrap()); + + self.raw.our_label = self + .our_label + .as_ref() + .map(|s| s.as_ptr()) + .unwrap_or(ptr::null()); + + self + } + + /// Specify ancestor label, default is "theirs" + pub fn their_label(&mut self, t: T) -> &mut MergeFileOptions { + self.their_label = Some(t.into_c_string().unwrap()); + + self.raw.their_label = self + .their_label + .as_ref() + .map(|s| s.as_ptr()) + .unwrap_or(ptr::null()); + + self + } + + /// Specify a side to favor for resolving conflicts + pub fn file_favor(&mut self, favor: FileFavor) -> &mut MergeFileOptions { + self.raw.favor = favor.convert(); + self + } + + /// Specify marker size, default is 7: <<<<<<< ours + pub fn marker_size(&mut self, size: u16) -> &mut MergeFileOptions { + self.raw.marker_size = size; + self + } + + /// Acquire a pointer to the underlying raw options. + pub unsafe fn raw(&self) -> *const raw::git_merge_file_options { + &self.raw as *const _ + } +} + +/// For git_merge_file_input +pub struct MergeFileInput { + raw: raw::git_merge_file_input, + + /// File name of the conflicted file, or `NULL` to not merge the path. + /// + /// You can turn this value into a `std::ffi::CString` with + /// `CString::new(&entry.path[..]).unwrap()`. To turn a reference into a + /// `&std::path::Path`, see the `bytes2path()` function in the private, + /// internal `util` module in this crate’s source code. + path: Option, + + /// File mode of the conflicted file, or `0` to not merge the mode. + pub mode: Option, + + /// File content + pub content: Option>, +} + +impl Default for MergeFileInput { + fn default() -> Self { + Self::new() + } +} + +impl MergeFileInput { + /// Creates a new set of empty diff options. + pub fn new() -> MergeFileInput { + let mut input = MergeFileInput { + raw: unsafe { mem::zeroed() }, + path: None, + mode: None, + content: None, + }; + assert_eq!( + unsafe { raw::git_merge_file_input_init(&mut input.raw, 1) }, + 0 + ); + input + } + + /// File name of the conflicted file, or `None` to not merge the path. + pub fn path(&mut self, t: T) -> &mut MergeFileInput { + self.path = Some(t.into_c_string().unwrap()); + + self.raw.path = self + .path + .as_ref() + .map(|s| s.as_ptr()) + .unwrap_or(ptr::null()); + + self + } + + /// File mode of the conflicted file, or `0` to not merge the mode. + pub fn mode(&mut self, mode: Option) -> &mut MergeFileInput { + self.mode = mode; + + if let Some(mode) = self.mode { + self.raw.mode = mode.into(); + } + + self + } + + /// File content, text or binary + pub fn content(&mut self, content: Option>) -> &mut MergeFileInput { + self.content = content; + + self.raw.size = self.content.as_ref().map(|c| c.len()).unwrap_or(0); + self.raw.ptr = self + .content + .as_ref() + .map(|c| c.as_ptr() as *const _) + .unwrap_or(ptr::null()); + + self + } + + /// Get the raw struct in C + pub fn raw(&self) -> *const raw::git_merge_file_input { + &self.raw as *const _ + } +} + +impl MergeFileInput { + /// Create from Repository and IndexEntry + pub fn from(repo: &Repository, index_entry: &IndexEntry) -> MergeFileInput { + let blob = repo + .find_blob(index_entry.id.clone()) + .expect("failed to find blob of index entry to make MergeFileInput"); + let content = blob.content().to_vec(); + + let mut input = MergeFileInput::new(); + input.content(Some(content)); + input.path(index_entry.path.clone()); + input.mode(Some(index_entry.mode.into())); + + input + } +} + +impl std::fmt::Debug for MergeFileInput { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + let mut ds = f.debug_struct("MergeFileInput"); + if let Some(path) = &self.path { + ds.field("path", path); + } + ds.field("mode", &self.mode); + + if let Some(mode) = self.mode { + match mode { + FileMode::Unreadable => {} + FileMode::Tree => {} + FileMode::Blob => { + let content = self + .content + .as_ref() + .map(|s| String::from_utf8_lossy(&s).to_string()) + .unwrap_or("unknown content".to_string()); + ds.field("content", &content); + } + FileMode::BlobExecutable => {} + FileMode::Link => {} + FileMode::Commit => {} + } + } + ds.finish() + } +} + +/// For git_merge_file_result +pub struct MergeFileResult { + /// True if the output was automerged, false if the output contains + /// conflict markers. + pub automergeable: bool, + + /// The path that the resultant merge file should use, or NULL if a + /// filename conflict would occur. + pub path: Option, + + /// The mode that the resultant merge file should use. + pub mode: FileMode, + + /// The contents of the merge. + pub content: Option>, +} + +impl MergeFileResult { + /// Create MergeFileResult from C + pub fn from_raw(raw: raw::git_merge_file_result) -> MergeFileResult { + let c_str: &CStr = unsafe { CStr::from_ptr(raw.path) }; + let str_slice: &str = c_str.to_str().unwrap(); + let path: String = str_slice.to_owned(); + + let content = + unsafe { slice::from_raw_parts(raw.ptr as *const u8, raw.len as usize).to_vec() }; + + MergeFileResult { + automergeable: raw.automergeable > 0, + path: Some(path), + mode: raw.mode.into(), + content: Some(content), + } + } +} + +impl std::fmt::Debug for MergeFileResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + let mut ds = f.debug_struct("MergeFileResult"); + if let Some(path) = &self.path { + ds.field("path", path); + } + ds.field("mode", &self.mode); + + match self.mode { + FileMode::Unreadable => {} + FileMode::Tree => {} + FileMode::Blob => unsafe { + let content = self + .content + .as_ref() + .map(|c| String::from_utf8_unchecked(c.clone())) + .unwrap_or("unknown content".to_string()); + ds.field("content", &content); + }, + FileMode::BlobExecutable => {} + FileMode::Link => {} + FileMode::Commit => {} + } + + ds.finish() + } +} diff --git a/src/repo.rs b/src/repo.rs index 113112e8a2..2a1bdf9027 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -11,13 +11,13 @@ use crate::build::{CheckoutBuilder, RepoBuilder}; use crate::diff::{ binary_cb_c, file_cb_c, hunk_cb_c, line_cb_c, BinaryCb, DiffCallbacks, FileCb, HunkCb, LineCb, }; +use crate::merge::{MergeFileInput, MergeFileOptions, MergeFileResult}; use crate::oid_array::OidArray; use crate::stash::{stash_cb, StashApplyOptions, StashCbData}; use crate::string_array::StringArray; use crate::tagforeach::{tag_foreach_cb, TagForeachCB, TagForeachData}; use crate::util::{self, path_to_repo_path, Binding}; use crate::worktree::{Worktree, WorktreeAddOptions}; -use crate::CherrypickOptions; use crate::RevertOptions; use crate::{ raw, AttrCheckFlags, Buf, Error, Object, Remote, RepositoryOpenFlags, RepositoryState, Revspec, @@ -29,6 +29,7 @@ use crate::{ use crate::{ApplyLocation, ApplyOptions, Rebase, RebaseOptions}; use crate::{Blame, BlameOptions, Reference, References, ResetType, Signature, Submodule}; use crate::{Blob, BlobWriter, Branch, BranchType, Branches, Commit, Config, Index, Oid, Tree}; +use crate::{CherrypickOptions, IndexEntry}; use crate::{Describe, IntoCString, Reflog, RepositoryInitMode, RevparseMode}; use crate::{DescribeOptions, Diff, DiffOptions, Odb, PackBuilder, TreeBuilder}; use crate::{Note, Notes, ObjectType, Revwalk, Status, StatusOptions, Statuses, Tag}; @@ -1840,6 +1841,80 @@ impl Repository { Ok(()) } + /// Merge two files as they exist in the in-memory data structures, using + /// the given common ancestor as the baseline, producing a + /// `git_merge_file_result` that reflects the merge result. The + /// `git_merge_file_result` must be freed with `git_merge_file_result_free`. + /// + /// Note that this function does not reference a repository and any + /// configuration must be passed as `git_merge_file_options`. + /// + /// @param out The git_merge_file_result to be filled in + /// @param ancestor The contents of the ancestor file + /// @param ours The contents of the file in "our" side + /// @param theirs The contents of the file in "their" side + /// @param opts The merge file options or `NULL` for defaults + /// @return 0 on success or error code + pub fn merge_file( + &self, + ancestor: Option<&IndexEntry>, + ours: Option<&IndexEntry>, + theirs: Option<&IndexEntry>, + options: Option<&MergeFileOptions>, + ) -> Result { + let ancestor_input; + let ours_input; + let theirs_input; + + let ancestor_raw; + let ours_raw; + let theirs_raw; + + if let Some(ancestor) = ancestor { + ancestor_input = MergeFileInput::from(&self, ancestor); + ancestor_raw = ancestor_input.raw(); + } else { + ancestor_raw = ptr::null(); + } + if let Some(ours) = ours { + ours_input = MergeFileInput::from(&self, ours); + ours_raw = ours_input.raw(); + } else { + ours_raw = ptr::null(); + } + if let Some(theirs) = theirs { + theirs_input = MergeFileInput::from(&self, theirs); + theirs_raw = theirs_input.raw(); + } else { + theirs_raw = ptr::null(); + } + + let mut ret = raw::git_merge_file_result { + automergeable: 0, + path: ptr::null(), + mode: 0, + ptr: ptr::null(), + len: 0, + }; + + unsafe { + try_call!(raw::git_merge_file( + &mut ret, + ancestor_raw, + ours_raw, + theirs_raw, + options.map(|o| o.raw()) + )); + + let result = MergeFileResult::from_raw(ret); + + // FIXME: need to free???? + raw::git_merge_file_result_free(&mut ret as *mut _); + + Ok(result) + } + } + /// Merges the given commit(s) into HEAD, writing the results into the /// working directory. Any changes are staged for commit and any conflicts /// are written to the index. Callers should inspect the repository's index @@ -3032,10 +3107,11 @@ impl RepositoryInitOptions { #[cfg(test)] mod tests { use crate::build::CheckoutBuilder; - use crate::CherrypickOptions; + use crate::{CherrypickOptions, FileMode}; use crate::{ObjectType, Oid, Repository, ResetType}; use std::ffi::OsStr; use std::fs; + use std::io::Write; use std::path::Path; use tempfile::TempDir; @@ -3256,6 +3332,154 @@ mod tests { assert_eq!(repo.head().unwrap().target().unwrap(), main_oid); } + /// merge files then return the result + #[test] + fn smoke_merge_file() { + let (_temp_dir, repo) = graph_repo_init(); + let sig = repo.signature().unwrap(); + + // let oid1 = head + let oid1 = repo.head().unwrap().target().unwrap(); + let commit1 = repo.find_commit(oid1).unwrap(); + println!("created oid1 {:?}", oid1); + + repo.branch("branch_a", &commit1, true).unwrap(); + repo.branch("branch_b", &commit1, true).unwrap(); + + let file_on_branch_a_content_1 = "111\n222\n333\n"; + let file_on_branch_a_content_2 = "bbb\nccc\nxxx\nyyy\nzzz"; + let file_on_branch_b_content_1 = "aaa\nbbb\nccc\n"; + let file_on_branch_b_content_2 = "ooo\nppp\nqqq\nkkk"; + let merge_file_result_content = "<<<<<<< file_a\n111\n222\n333\nbbb\nccc\nxxx\nyyy\nzzz\n=======\naaa\nbbb\nccc\nooo\nppp\nqqq\nkkk\n>>>>>>> file_a\n"; + + // create commit oid2 on branchA + let mut index = repo.index().unwrap(); + let p = Path::new(repo.workdir().unwrap()).join("file_a"); + println!("using path {:?}", p); + let mut file_a = fs::File::create(&p).unwrap(); + file_a + .write_all(file_on_branch_a_content_1.as_bytes()) + .unwrap(); + index.add_path(Path::new("file_a")).unwrap(); + let id_a = index.write_tree().unwrap(); + let tree_a = repo.find_tree(id_a).unwrap(); + let oid2 = repo + .commit( + Some("refs/heads/branch_a"), + &sig, + &sig, + "commit 2", + &tree_a, + &[&commit1], + ) + .unwrap(); + let commit2 = repo.find_commit(oid2).unwrap(); + println!("created oid2 {:?}", oid2); + + t!(repo.reset(commit1.as_object(), ResetType::Hard, None)); + + // create commit oid3 on branchB + let mut index = repo.index().unwrap(); + let p = Path::new(repo.workdir().unwrap()).join("file_a"); + let mut file_b = fs::File::create(&p).unwrap(); + file_b + .write_all(file_on_branch_b_content_1.as_bytes()) + .unwrap(); + index.add_path(Path::new("file_a")).unwrap(); + let id_b = index.write_tree().unwrap(); + let tree_b = repo.find_tree(id_b).unwrap(); + let oid3 = repo + .commit( + Some("refs/heads/branch_b"), + &sig, + &sig, + "commit 3", + &tree_b, + &[&commit1], + ) + .unwrap(); + let commit3 = repo.find_commit(oid3).unwrap(); + println!("created oid3 {:?}", oid3); + + t!(repo.reset(commit2.as_object(), ResetType::Hard, None)); + + // create commit oid4 on branchA + let mut index = repo.index().unwrap(); + let p = Path::new(repo.workdir().unwrap()).join("file_a"); + let mut file_a = fs::OpenOptions::new().append(true).open(&p).unwrap(); + file_a.write(file_on_branch_a_content_2.as_bytes()).unwrap(); + index.add_path(Path::new("file_a")).unwrap(); + let id_a_2 = index.write_tree().unwrap(); + let tree_a_2 = repo.find_tree(id_a_2).unwrap(); + let oid4 = repo + .commit( + Some("refs/heads/branch_a"), + &sig, + &sig, + "commit 4", + &tree_a_2, + &[&commit2], + ) + .unwrap(); + let commit4 = repo.find_commit(oid4).unwrap(); + println!("created oid4 {:?}", oid4); + + t!(repo.reset(commit3.as_object(), ResetType::Hard, None)); + + // create commit oid4 on branchB + let mut index = repo.index().unwrap(); + let p = Path::new(repo.workdir().unwrap()).join("file_a"); + let mut file_a = fs::OpenOptions::new().append(true).open(&p).unwrap(); + file_a.write(file_on_branch_b_content_2.as_bytes()).unwrap(); + index.add_path(Path::new("file_a")).unwrap(); + let id_b_2 = index.write_tree().unwrap(); + let tree_b_2 = repo.find_tree(id_b_2).unwrap(); + let oid5 = repo + .commit( + Some("refs/heads/branch_b"), + &sig, + &sig, + "commit 5", + &tree_b_2, + &[&commit3], + ) + .unwrap(); + let commit5 = repo.find_commit(oid5).unwrap(); + println!("created oid5 {:?}", oid5); + + // create merge commit oid4 on branchA with parents oid2 and oid3 + //let mut index4 = repo.merge_commits(&commit2, &commit3, None).unwrap(); + repo.set_head("refs/heads/branch_a").unwrap(); + repo.checkout_head(None).unwrap(); + + let index = repo.merge_commits(&commit4, &commit5, None).unwrap(); + + assert!(index.has_conflicts(), "index should have conflicts"); + + let index_conflicts = index.conflicts().unwrap(); + for conflict in index_conflicts { + let conflict = conflict.unwrap(); + + let merge_file_result = repo + .merge_file( + conflict.ancestor.as_ref(), + conflict.our.as_ref(), + conflict.their.as_ref(), + None, + ) + .unwrap(); + + assert_eq!(merge_file_result.mode, FileMode::Blob); + assert_eq!(merge_file_result.path.unwrap().as_str(), "file_a"); + assert_eq!( + String::from_utf8(merge_file_result.content.unwrap()) + .unwrap() + .as_str(), + merge_file_result_content + ); + } + } + /// create the following: /// /---o4 /// /---o3 From 6ba9e9652c9c51ff7bf303a9a28f4b25341ac3fc Mon Sep 17 00:00:00 2001 From: K Date: Tue, 24 Nov 2020 10:47:46 +0800 Subject: [PATCH 2/5] Improvements --- .gitignore | 4 ++ libgit2-sys/lib.rs | 152 ++++++++++++++++++++++----------------------- src/lib.rs | 16 +---- src/merge.rs | 6 +- src/repo.rs | 3 - 5 files changed, 83 insertions(+), 98 deletions(-) diff --git a/.gitignore b/.gitignore index 2a729c83fa..74bfa406dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ target Cargo.lock src/main.rs + +.vscode/ +.history/ +.idea/ \ No newline at end of file diff --git a/libgit2-sys/lib.rs b/libgit2-sys/lib.rs index ea19d0cf96..7693438f08 100644 --- a/libgit2-sys/lib.rs +++ b/libgit2-sys/lib.rs @@ -1306,6 +1306,65 @@ git_enum! { } } +#[repr(C)] +pub struct git_merge_file_options { + pub version: c_uint, + + /// Label for the ancestor file side of the conflict which will be prepended + /// to labels in diff3-format merge files. + pub ancestor_label: *const c_char, + + /// Label for our file side of the conflict which will be prepended + /// to labels in merge files. + pub our_label: *const c_char, + + /// Label for their file side of the conflict which will be prepended + /// to labels in merge files. + pub their_label: *const c_char, + + /// The file to favor in region conflicts. + pub favor: git_merge_file_favor_t, + + /// see `git_merge_file_flag_t` + pub flags: c_uint, + pub marker_size: c_ushort, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct git_merge_file_input { + pub version: c_uint, + /// Pointer to the contents of the file. + pub ptr: *const c_char, + /// Size of the contents pointed to in `ptr`. + pub size: size_t, + /// File name of the conflicted file, or `NULL` to not merge the path. + pub path: *const c_char, + /// File mode of the conflicted file, or `0` to not merge the mode. + pub mode: c_uint, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct git_merge_file_result { + /// True if the output was automerged, false if the output contains + /// conflict markers. + pub automergeable: c_uint, + + /// The path that the resultant merge file should use, or NULL if a + /// filename conflict would occur. + pub path: *const c_char, + + /// The mode that the resultant merge file should use. + pub mode: c_uint, + + /// The contents of the merge. + pub ptr: *const c_char, + + /// The length of the merge contents. + pub len: size_t, +} + pub type git_transport_cb = Option< extern "C" fn( out: *mut *mut git_transport, @@ -3073,7 +3132,6 @@ extern "C" { pub fn git_repository_state_cleanup(repo: *mut git_repository) -> c_int; // merge analysis - pub fn git_merge_analysis( analysis_out: *mut git_merge_analysis_t, pref_out: *mut git_merge_preference_t, @@ -3082,6 +3140,22 @@ extern "C" { their_heads_len: usize, ) -> c_int; + // For git_merge_file + pub fn git_merge_file_options_init(opts: *mut git_merge_file_options, version: c_uint) + -> c_int; + pub fn git_merge_file_input_init(opts: *mut git_merge_file_input, version: c_uint) -> c_int; + + pub fn git_merge_file( + out: *mut git_merge_file_result, + ancestor: *const git_merge_file_input, + ours: *const git_merge_file_input, + theirs: *const git_merge_file_input, + opts: *const git_merge_file_options, + ) -> c_int; + + // Not used? + pub fn git_merge_file_result_free(result: *mut git_merge_file_result); + // notes pub fn git_note_author(note: *const git_note) -> *const git_signature; pub fn git_note_committer(note: *const git_note) -> *const git_signature; @@ -3855,82 +3929,6 @@ extern "C" { ) -> c_int; } -#[repr(C)] -pub struct git_merge_file_options { - pub version: c_uint, - - /// Label for the ancestor file side of the conflict which will be prepended - /// to labels in diff3-format merge files. - pub ancestor_label: *const c_char, - - /// Label for our file side of the conflict which will be prepended - /// to labels in merge files. - pub our_label: *const c_char, - - /// Label for their file side of the conflict which will be prepended - /// to labels in merge files. - pub their_label: *const c_char, - - /// The file to favor in region conflicts. - pub favor: git_merge_file_favor_t, - - /// see `git_merge_file_flag_t` - pub flags: c_uint, - pub marker_size: c_ushort, -} - -#[repr(C)] -#[derive(Copy, Clone)] -pub struct git_merge_file_input { - pub version: c_uint, - /// Pointer to the contents of the file. - pub ptr: *const c_char, - /// Size of the contents pointed to in `ptr`. - pub size: size_t, - /// File name of the conflicted file, or `NULL` to not merge the path. - pub path: *const c_char, - /// File mode of the conflicted file, or `0` to not merge the mode. - pub mode: c_uint, -} - -#[repr(C)] -#[derive(Copy, Clone)] -pub struct git_merge_file_result { - /// True if the output was automerged, false if the output contains - /// conflict markers. - pub automergeable: c_uint, - - /// The path that the resultant merge file should use, or NULL if a - /// filename conflict would occur. - pub path: *const c_char, - - /// The mode that the resultant merge file should use. - pub mode: c_uint, - - /// The contents of the merge. - pub ptr: *const c_char, - - /// The length of the merge contents. - pub len: size_t, -} - -extern "C" { - pub fn git_merge_file_options_init(opts: *mut git_merge_file_options, version: c_uint) - -> c_int; - pub fn git_merge_file_input_init(opts: *mut git_merge_file_input, version: c_uint) -> c_int; - - pub fn git_merge_file( - out: *mut git_merge_file_result, - ancestor: *const git_merge_file_input, - ours: *const git_merge_file_input, - theirs: *const git_merge_file_input, - opts: *const git_merge_file_options, - ) -> c_int; - - // Not used? - pub fn git_merge_file_result_free(result: *mut git_merge_file_result); -} - pub fn init() { use std::sync::Once; diff --git a/src/lib.rs b/src/lib.rs index eeb484eb3c..fd82ccab13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1051,7 +1051,7 @@ pub enum FileMode { Commit, } -impl From for FileMode { +impl FileMode { fn from(mode: u32) -> Self { match mode { raw::GIT_FILEMODE_UNREADABLE => FileMode::Unreadable, @@ -1065,20 +1065,6 @@ impl From for FileMode { } } -impl Into for FileMode { - fn into(self) -> u32 { - let ret = match self { - FileMode::Unreadable => raw::GIT_FILEMODE_UNREADABLE, - FileMode::Tree => raw::GIT_FILEMODE_TREE, - FileMode::Blob => raw::GIT_FILEMODE_BLOB, - FileMode::BlobExecutable => raw::GIT_FILEMODE_BLOB_EXECUTABLE, - FileMode::Link => raw::GIT_FILEMODE_LINK, - FileMode::Commit => raw::GIT_FILEMODE_COMMIT, - }; - ret as u32 - } -} - bitflags! { /// Return codes for submodule status. /// diff --git a/src/merge.rs b/src/merge.rs index 1a211c5dfd..dbfc084c85 100644 --- a/src/merge.rs +++ b/src/merge.rs @@ -351,7 +351,7 @@ impl MergeFileInput { self.mode = mode; if let Some(mode) = self.mode { - self.raw.mode = mode.into(); + self.raw.mode = mode as u32; } self @@ -388,7 +388,7 @@ impl MergeFileInput { let mut input = MergeFileInput::new(); input.content(Some(content)); input.path(index_entry.path.clone()); - input.mode(Some(index_entry.mode.into())); + input.mode(Some(FileMode::from(index_entry.mode))); input } @@ -453,7 +453,7 @@ impl MergeFileResult { MergeFileResult { automergeable: raw.automergeable > 0, path: Some(path), - mode: raw.mode.into(), + mode: FileMode::from(raw.mode), content: Some(content), } } diff --git a/src/repo.rs b/src/repo.rs index 2a1bdf9027..b650e20dda 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -1908,9 +1908,6 @@ impl Repository { let result = MergeFileResult::from_raw(ret); - // FIXME: need to free???? - raw::git_merge_file_result_free(&mut ret as *mut _); - Ok(result) } } From 2b5b1dd48d77b7f3bbd4b2e7169c3ddc4ab584d4 Mon Sep 17 00:00:00 2001 From: K Date: Thu, 26 Nov 2020 12:37:33 +0800 Subject: [PATCH 3/5] Improvements --- src/lib.rs | 14 ++++++ src/merge.rs | 86 ++++++++++++++----------------------- src/repo.rs | 119 ++++++++++++++++++++++++++++++--------------------- 3 files changed, 116 insertions(+), 103 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fd82ccab13..8b935325d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1052,6 +1052,20 @@ pub enum FileMode { } impl FileMode { + #[cfg(target_os = "windows")] + fn from(mode: i32) -> Self { + match mode { + raw::GIT_FILEMODE_UNREADABLE => FileMode::Unreadable, + raw::GIT_FILEMODE_TREE => FileMode::Tree, + raw::GIT_FILEMODE_BLOB => FileMode::Blob, + raw::GIT_FILEMODE_BLOB_EXECUTABLE => FileMode::BlobExecutable, + raw::GIT_FILEMODE_LINK => FileMode::Link, + raw::GIT_FILEMODE_COMMIT => FileMode::Commit, + mode => panic!("unknown file mode: {}", mode), + } + } + + #[cfg(not(target_os = "windows"))] fn from(mode: u32) -> Self { match mode { raw::GIT_FILEMODE_UNREADABLE => FileMode::Unreadable, diff --git a/src/merge.rs b/src/merge.rs index dbfc084c85..c64314f23f 100644 --- a/src/merge.rs +++ b/src/merge.rs @@ -5,8 +5,9 @@ use std::str; use crate::call::Convert; use crate::util::Binding; -use crate::{raw, Commit, FileFavor, FileMode, IndexEntry, IntoCString, Oid, Repository}; +use crate::{raw, Commit, FileFavor, FileMode, IntoCString, Oid}; use core::{ptr, slice}; +use std::convert::TryInto; use std::ffi::{CStr, CString}; /// A structure to represent an annotated commit, the input to merge and rebase. @@ -293,7 +294,7 @@ impl MergeFileOptions { } /// For git_merge_file_input -pub struct MergeFileInput { +pub struct MergeFileInput<'a> { raw: raw::git_merge_file_input, /// File name of the conflicted file, or `NULL` to not merge the path. @@ -304,26 +305,22 @@ pub struct MergeFileInput { /// internal `util` module in this crate’s source code. path: Option, - /// File mode of the conflicted file, or `0` to not merge the mode. - pub mode: Option, - /// File content - pub content: Option>, + content: Option<&'a [u8]>, } -impl Default for MergeFileInput { +impl Default for MergeFileInput<'_> { fn default() -> Self { Self::new() } } -impl MergeFileInput { +impl<'a> MergeFileInput<'a> { /// Creates a new set of empty diff options. - pub fn new() -> MergeFileInput { + pub fn new() -> MergeFileInput<'a> { let mut input = MergeFileInput { raw: unsafe { mem::zeroed() }, path: None, - mode: None, content: None, }; assert_eq!( @@ -334,7 +331,7 @@ impl MergeFileInput { } /// File name of the conflicted file, or `None` to not merge the path. - pub fn path(&mut self, t: T) -> &mut MergeFileInput { + pub fn path(&mut self, t: T) -> &mut MergeFileInput<'a> { self.path = Some(t.into_c_string().unwrap()); self.raw.path = self @@ -347,10 +344,8 @@ impl MergeFileInput { } /// File mode of the conflicted file, or `0` to not merge the mode. - pub fn mode(&mut self, mode: Option) -> &mut MergeFileInput { - self.mode = mode; - - if let Some(mode) = self.mode { + pub fn mode(&mut self, mode: Option) -> &mut MergeFileInput<'a> { + if let Some(mode) = mode { self.raw.mode = mode as u32; } @@ -358,7 +353,7 @@ impl MergeFileInput { } /// File content, text or binary - pub fn content(&mut self, content: Option>) -> &mut MergeFileInput { + pub fn content(&mut self, content: Option<&'a [u8]>) -> &mut MergeFileInput<'a> { self.content = content; self.raw.size = self.content.as_ref().map(|c| c.len()).unwrap_or(0); @@ -377,47 +372,29 @@ impl MergeFileInput { } } -impl MergeFileInput { - /// Create from Repository and IndexEntry - pub fn from(repo: &Repository, index_entry: &IndexEntry) -> MergeFileInput { - let blob = repo - .find_blob(index_entry.id.clone()) - .expect("failed to find blob of index entry to make MergeFileInput"); - let content = blob.content().to_vec(); - - let mut input = MergeFileInput::new(); - input.content(Some(content)); - input.path(index_entry.path.clone()); - input.mode(Some(FileMode::from(index_entry.mode))); - - input - } -} - -impl std::fmt::Debug for MergeFileInput { +impl std::fmt::Debug for MergeFileInput<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { let mut ds = f.debug_struct("MergeFileInput"); if let Some(path) = &self.path { ds.field("path", path); } - ds.field("mode", &self.mode); + ds.field("mode", &FileMode::from(self.raw.mode.try_into().unwrap())); - if let Some(mode) = self.mode { - match mode { - FileMode::Unreadable => {} - FileMode::Tree => {} - FileMode::Blob => { - let content = self - .content - .as_ref() - .map(|s| String::from_utf8_lossy(&s).to_string()) - .unwrap_or("unknown content".to_string()); - ds.field("content", &content); - } - FileMode::BlobExecutable => {} - FileMode::Link => {} - FileMode::Commit => {} + match FileMode::from(self.raw.mode.try_into().unwrap()) { + FileMode::Unreadable => {} + FileMode::Tree => {} + FileMode::Blob => { + let content = self + .content + .as_ref() + .map(|s| String::from_utf8_lossy(&s).to_string()) + .unwrap_or("unknown content".to_string()); + + ds.field("content", &content); } + FileMode::BlobExecutable => {} + FileMode::Link => {} + FileMode::Commit => {} } ds.finish() } @@ -442,18 +419,17 @@ pub struct MergeFileResult { impl MergeFileResult { /// Create MergeFileResult from C - pub fn from_raw(raw: raw::git_merge_file_result) -> MergeFileResult { - let c_str: &CStr = unsafe { CStr::from_ptr(raw.path) }; + pub unsafe fn from_raw(raw: raw::git_merge_file_result) -> MergeFileResult { + let c_str: &CStr = CStr::from_ptr(raw.path); let str_slice: &str = c_str.to_str().unwrap(); let path: String = str_slice.to_owned(); - let content = - unsafe { slice::from_raw_parts(raw.ptr as *const u8, raw.len as usize).to_vec() }; + let content = slice::from_raw_parts(raw.ptr as *const u8, raw.len as usize).to_vec(); MergeFileResult { automergeable: raw.automergeable > 0, path: Some(path), - mode: FileMode::from(raw.mode), + mode: FileMode::from(raw.mode.try_into().unwrap()), content: Some(content), } } diff --git a/src/repo.rs b/src/repo.rs index b650e20dda..b8f01972a3 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -18,6 +18,7 @@ use crate::string_array::StringArray; use crate::tagforeach::{tag_foreach_cb, TagForeachCB, TagForeachData}; use crate::util::{self, path_to_repo_path, Binding}; use crate::worktree::{Worktree, WorktreeAddOptions}; +use crate::CherrypickOptions; use crate::RevertOptions; use crate::{ raw, AttrCheckFlags, Buf, Error, Object, Remote, RepositoryOpenFlags, RepositoryState, Revspec, @@ -29,7 +30,6 @@ use crate::{ use crate::{ApplyLocation, ApplyOptions, Rebase, RebaseOptions}; use crate::{Blame, BlameOptions, Reference, References, ResetType, Signature, Submodule}; use crate::{Blob, BlobWriter, Branch, BranchType, Branches, Commit, Config, Index, Oid, Tree}; -use crate::{CherrypickOptions, IndexEntry}; use crate::{Describe, IntoCString, Reflog, RepositoryInitMode, RevparseMode}; use crate::{DescribeOptions, Diff, DiffOptions, Odb, PackBuilder, TreeBuilder}; use crate::{Note, Notes, ObjectType, Revwalk, Status, StatusOptions, Statuses, Tag}; @@ -1848,47 +1848,13 @@ impl Repository { /// /// Note that this function does not reference a repository and any /// configuration must be passed as `git_merge_file_options`. - /// - /// @param out The git_merge_file_result to be filled in - /// @param ancestor The contents of the ancestor file - /// @param ours The contents of the file in "our" side - /// @param theirs The contents of the file in "their" side - /// @param opts The merge file options or `NULL` for defaults - /// @return 0 on success or error code pub fn merge_file( &self, - ancestor: Option<&IndexEntry>, - ours: Option<&IndexEntry>, - theirs: Option<&IndexEntry>, + ancestor: Option<&MergeFileInput<'_>>, + ours: Option<&MergeFileInput<'_>>, + theirs: Option<&MergeFileInput<'_>>, options: Option<&MergeFileOptions>, ) -> Result { - let ancestor_input; - let ours_input; - let theirs_input; - - let ancestor_raw; - let ours_raw; - let theirs_raw; - - if let Some(ancestor) = ancestor { - ancestor_input = MergeFileInput::from(&self, ancestor); - ancestor_raw = ancestor_input.raw(); - } else { - ancestor_raw = ptr::null(); - } - if let Some(ours) = ours { - ours_input = MergeFileInput::from(&self, ours); - ours_raw = ours_input.raw(); - } else { - ours_raw = ptr::null(); - } - if let Some(theirs) = theirs { - theirs_input = MergeFileInput::from(&self, theirs); - theirs_raw = theirs_input.raw(); - } else { - theirs_raw = ptr::null(); - } - let mut ret = raw::git_merge_file_result { automergeable: 0, path: ptr::null(), @@ -1900,9 +1866,9 @@ impl Repository { unsafe { try_call!(raw::git_merge_file( &mut ret, - ancestor_raw, - ours_raw, - theirs_raw, + ancestor.map(|a| a.raw()).unwrap_or(ptr::null()), + ours.map(|a| a.raw()).unwrap_or(ptr::null()), + theirs.map(|a| a.raw()).unwrap_or(ptr::null()), options.map(|o| o.raw()) )); @@ -3104,8 +3070,9 @@ impl RepositoryInitOptions { #[cfg(test)] mod tests { use crate::build::CheckoutBuilder; - use crate::{CherrypickOptions, FileMode}; + use crate::{CherrypickOptions, FileMode, MergeFileInput}; use crate::{ObjectType, Oid, Repository, ResetType}; + use std::convert::TryInto; use std::ffi::OsStr; use std::fs; use std::io::Write; @@ -3357,6 +3324,7 @@ mod tests { file_a .write_all(file_on_branch_a_content_1.as_bytes()) .unwrap(); + drop(file_a); index.add_path(Path::new("file_a")).unwrap(); let id_a = index.write_tree().unwrap(); let tree_a = repo.find_tree(id_a).unwrap(); @@ -3378,10 +3346,11 @@ mod tests { // create commit oid3 on branchB let mut index = repo.index().unwrap(); let p = Path::new(repo.workdir().unwrap()).join("file_a"); - let mut file_b = fs::File::create(&p).unwrap(); - file_b + let mut file_a = fs::File::create(&p).unwrap(); + file_a .write_all(file_on_branch_b_content_1.as_bytes()) .unwrap(); + drop(file_a); index.add_path(Path::new("file_a")).unwrap(); let id_b = index.write_tree().unwrap(); let tree_b = repo.find_tree(id_b).unwrap(); @@ -3405,6 +3374,7 @@ mod tests { let p = Path::new(repo.workdir().unwrap()).join("file_a"); let mut file_a = fs::OpenOptions::new().append(true).open(&p).unwrap(); file_a.write(file_on_branch_a_content_2.as_bytes()).unwrap(); + drop(file_a); index.add_path(Path::new("file_a")).unwrap(); let id_a_2 = index.write_tree().unwrap(); let tree_a_2 = repo.find_tree(id_a_2).unwrap(); @@ -3423,11 +3393,12 @@ mod tests { t!(repo.reset(commit3.as_object(), ResetType::Hard, None)); - // create commit oid4 on branchB + // create commit oid5 on branchB let mut index = repo.index().unwrap(); let p = Path::new(repo.workdir().unwrap()).join("file_a"); let mut file_a = fs::OpenOptions::new().append(true).open(&p).unwrap(); file_a.write(file_on_branch_b_content_2.as_bytes()).unwrap(); + drop(file_a); index.add_path(Path::new("file_a")).unwrap(); let id_b_2 = index.write_tree().unwrap(); let tree_b_2 = repo.find_tree(id_b_2).unwrap(); @@ -3457,11 +3428,63 @@ mod tests { for conflict in index_conflicts { let conflict = conflict.unwrap(); + let ancestor_input; + let ours_input; + let theirs_input; + + let ancestor_blob; + let ours_blob; + let theirs_blob; + + let ancestor_content; + let ours_content; + let theirs_content; + + if let Some(ancestor) = conflict.ancestor { + ancestor_blob = repo + .find_blob(ancestor.id.clone()) + .expect("failed to find blob of index entry to make MergeFileInput"); + ancestor_content = ancestor_blob.content(); + let mut input = MergeFileInput::new(); + input.path(String::from_utf8(ancestor.path).unwrap()); + input.mode(Some(FileMode::from(ancestor.mode.try_into().unwrap()))); + input.content(Some(&ancestor_content)); + ancestor_input = Some(input); + } else { + ancestor_input = None; + } + if let Some(ours) = conflict.our { + ours_blob = repo + .find_blob(ours.id.clone()) + .expect("failed to find blob of index entry to make MergeFileInput"); + ours_content = ours_blob.content(); + let mut input = MergeFileInput::new(); + input.path(String::from_utf8(ours.path).unwrap()); + input.mode(Some(FileMode::from(ours.mode.try_into().unwrap()))); + input.content(Some(&ours_content)); + ours_input = Some(input); + } else { + ours_input = None; + } + if let Some(theirs) = conflict.their { + theirs_blob = repo + .find_blob(theirs.id.clone()) + .expect("failed to find blob of index entry to make MergeFileInput"); + theirs_content = theirs_blob.content(); + let mut input = MergeFileInput::new(); + input.path(String::from_utf8(theirs.path).unwrap()); + input.mode(Some(FileMode::from(theirs.mode.try_into().unwrap()))); + input.content(Some(&theirs_content)); + theirs_input = Some(input); + } else { + theirs_input = None; + } + let merge_file_result = repo .merge_file( - conflict.ancestor.as_ref(), - conflict.our.as_ref(), - conflict.their.as_ref(), + ancestor_input.as_ref(), + ours_input.as_ref(), + theirs_input.as_ref(), None, ) .unwrap(); From 0736d2ca00dd182260a3833ed13866db533d6cf7 Mon Sep 17 00:00:00 2001 From: K Date: Thu, 26 Nov 2020 14:46:06 +0800 Subject: [PATCH 4/5] Improve --- src/merge.rs | 58 +++++++++++++++++++++++++++++----------------------- src/repo.rs | 25 +++++++++++----------- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/src/merge.rs b/src/merge.rs index c64314f23f..0326ff5e32 100644 --- a/src/merge.rs +++ b/src/merge.rs @@ -402,53 +402,59 @@ impl std::fmt::Debug for MergeFileInput<'_> { /// For git_merge_file_result pub struct MergeFileResult { - /// True if the output was automerged, false if the output contains - /// conflict markers. - pub automergeable: bool, - - /// The path that the resultant merge file should use, or NULL if a - /// filename conflict would occur. - pub path: Option, - - /// The mode that the resultant merge file should use. - pub mode: FileMode, - - /// The contents of the merge. - pub content: Option>, + raw: raw::git_merge_file_result, } impl MergeFileResult { /// Create MergeFileResult from C pub unsafe fn from_raw(raw: raw::git_merge_file_result) -> MergeFileResult { - let c_str: &CStr = CStr::from_ptr(raw.path); + MergeFileResult { raw } + } + + /// True if the output was automerged, false if the output contains + /// conflict markers. + pub fn automergeable(&self) -> bool { + self.raw.automergeable > 0 + } + + /// The path that the resultant merge file should use, or NULL if a + /// filename conflict would occur. + pub unsafe fn path(&self) -> Option { + let c_str: &CStr = CStr::from_ptr(self.raw.path); let str_slice: &str = c_str.to_str().unwrap(); let path: String = str_slice.to_owned(); + Some(path) + } - let content = slice::from_raw_parts(raw.ptr as *const u8, raw.len as usize).to_vec(); + /// The mode that the resultant merge file should use. + pub fn mode(&self) -> FileMode { + FileMode::from(self.raw.mode.try_into().unwrap()) + } - MergeFileResult { - automergeable: raw.automergeable > 0, - path: Some(path), - mode: FileMode::from(raw.mode.try_into().unwrap()), - content: Some(content), - } + /// The contents of the merge. + pub unsafe fn content(&self) -> Option> { + let content = + slice::from_raw_parts(self.raw.ptr as *const u8, self.raw.len as usize).to_vec(); + Some(content) } } impl std::fmt::Debug for MergeFileResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { let mut ds = f.debug_struct("MergeFileResult"); - if let Some(path) = &self.path { - ds.field("path", path); + unsafe { + if let Some(path) = &self.path() { + ds.field("path", path); + } } - ds.field("mode", &self.mode); + ds.field("mode", &self.mode()); - match self.mode { + match self.mode() { FileMode::Unreadable => {} FileMode::Tree => {} FileMode::Blob => unsafe { let content = self - .content + .content() .as_ref() .map(|c| String::from_utf8_unchecked(c.clone())) .unwrap_or("unknown content".to_string()); diff --git a/src/repo.rs b/src/repo.rs index b8f01972a3..0cc5f32a7c 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -3436,15 +3436,11 @@ mod tests { let ours_blob; let theirs_blob; - let ancestor_content; - let ours_content; - let theirs_content; - if let Some(ancestor) = conflict.ancestor { ancestor_blob = repo .find_blob(ancestor.id.clone()) .expect("failed to find blob of index entry to make MergeFileInput"); - ancestor_content = ancestor_blob.content(); + let ancestor_content = ancestor_blob.content(); let mut input = MergeFileInput::new(); input.path(String::from_utf8(ancestor.path).unwrap()); input.mode(Some(FileMode::from(ancestor.mode.try_into().unwrap()))); @@ -3457,7 +3453,7 @@ mod tests { ours_blob = repo .find_blob(ours.id.clone()) .expect("failed to find blob of index entry to make MergeFileInput"); - ours_content = ours_blob.content(); + let ours_content = ours_blob.content(); let mut input = MergeFileInput::new(); input.path(String::from_utf8(ours.path).unwrap()); input.mode(Some(FileMode::from(ours.mode.try_into().unwrap()))); @@ -3470,7 +3466,7 @@ mod tests { theirs_blob = repo .find_blob(theirs.id.clone()) .expect("failed to find blob of index entry to make MergeFileInput"); - theirs_content = theirs_blob.content(); + let theirs_content = theirs_blob.content(); let mut input = MergeFileInput::new(); input.path(String::from_utf8(theirs.path).unwrap()); input.mode(Some(FileMode::from(theirs.mode.try_into().unwrap()))); @@ -3489,12 +3485,17 @@ mod tests { ) .unwrap(); - assert_eq!(merge_file_result.mode, FileMode::Blob); - assert_eq!(merge_file_result.path.unwrap().as_str(), "file_a"); + assert_eq!(merge_file_result.mode(), FileMode::Blob); + assert_eq!( + unsafe { merge_file_result.path().unwrap().as_str() }, + "file_a" + ); assert_eq!( - String::from_utf8(merge_file_result.content.unwrap()) - .unwrap() - .as_str(), + unsafe { + String::from_utf8(merge_file_result.content().unwrap()) + .unwrap() + .as_str() + }, merge_file_result_content ); } From ed71b2d601cc1ac41874dd4bd61db25b10108006 Mon Sep 17 00:00:00 2001 From: Kent Krantz Date: Fri, 27 Nov 2020 13:14:06 +0800 Subject: [PATCH 5/5] Fixed bug of smoke_merge_file --- src/repo.rs | 74 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/src/repo.rs b/src/repo.rs index 0cc5f32a7c..49f8d0d90f 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -3424,6 +3424,8 @@ mod tests { assert!(index.has_conflicts(), "index should have conflicts"); + let mut conflict_count = 0; + let index_conflicts = index.conflicts().unwrap(); for conflict in index_conflicts { let conflict = conflict.unwrap(); @@ -3437,41 +3439,56 @@ mod tests { let theirs_blob; if let Some(ancestor) = conflict.ancestor { - ancestor_blob = repo - .find_blob(ancestor.id.clone()) - .expect("failed to find blob of index entry to make MergeFileInput"); - let ancestor_content = ancestor_blob.content(); - let mut input = MergeFileInput::new(); - input.path(String::from_utf8(ancestor.path).unwrap()); - input.mode(Some(FileMode::from(ancestor.mode.try_into().unwrap()))); - input.content(Some(&ancestor_content)); - ancestor_input = Some(input); + match repo.find_blob(ancestor.id.clone()) { + Ok(b) => { + ancestor_blob = b; + let ancestor_content = ancestor_blob.content(); + let mut input = MergeFileInput::new(); + input.path(String::from_utf8(ancestor.path).unwrap()); + input.mode(Some(FileMode::from(ancestor.mode.try_into().unwrap()))); + input.content(Some(&ancestor_content)); + ancestor_input = Some(input); + } + Err(_e) => { + ancestor_input = None; + } + } } else { ancestor_input = None; } if let Some(ours) = conflict.our { - ours_blob = repo - .find_blob(ours.id.clone()) - .expect("failed to find blob of index entry to make MergeFileInput"); - let ours_content = ours_blob.content(); - let mut input = MergeFileInput::new(); - input.path(String::from_utf8(ours.path).unwrap()); - input.mode(Some(FileMode::from(ours.mode.try_into().unwrap()))); - input.content(Some(&ours_content)); - ours_input = Some(input); + match repo.find_blob(ours.id.clone()) { + Ok(b) => { + ours_blob = b; + let ours_content = ours_blob.content(); + let mut input = MergeFileInput::new(); + input.path(String::from_utf8(ours.path).unwrap()); + input.mode(Some(FileMode::from(ours.mode.try_into().unwrap()))); + input.content(Some(&ours_content)); + ours_input = Some(input); + } + Err(_e) => { + ours_input = None; + } + } } else { ours_input = None; } if let Some(theirs) = conflict.their { - theirs_blob = repo - .find_blob(theirs.id.clone()) - .expect("failed to find blob of index entry to make MergeFileInput"); - let theirs_content = theirs_blob.content(); - let mut input = MergeFileInput::new(); - input.path(String::from_utf8(theirs.path).unwrap()); - input.mode(Some(FileMode::from(theirs.mode.try_into().unwrap()))); - input.content(Some(&theirs_content)); - theirs_input = Some(input); + match repo.find_blob(theirs.id.clone()) { + Ok(b) => { + theirs_blob = b; + let theirs_content = theirs_blob.content(); + let mut input = MergeFileInput::new(); + input.path(String::from_utf8(theirs.path).unwrap()); + input.mode(Some(FileMode::from(theirs.mode.try_into().unwrap()))); + input.content(Some(&theirs_content)); + theirs_input = Some(input); + } + Err(_e) => { + theirs_input = None; + } + } } else { theirs_input = None; } @@ -3498,7 +3515,10 @@ mod tests { }, merge_file_result_content ); + + conflict_count += 1; } + assert_eq!(conflict_count, 1, "There should be one conflict!"); } /// create the following: